Compare commits

..

5 Commits

Author SHA1 Message Date
b6eb502e56 Fixing .deb package generation, I suppose 2026-03-06 01:08:34 -04:00
596ad367e2 dbg:Gridlock detection: exclude locks where hasBeenReleased=true 2026-03-05 23:29:48 -04:00
e4332323f9 CompThr: Set keepLooping=false in ctor 2026-03-05 23:29:10 -04:00
7eff7a6a9c Dbg:traceContinHistForDeadlock: consult LockUsageDesc::hasBeenReleased
Don't raise the alarm for a deadlock if the lock has already been
released at the moment of checking.
2026-03-05 23:10:05 -04:00
85ac715772 SerializedAC:analyze all locksets in contin chain;
Not only those of type <OriginCbT>.
* Fix indentation too.
2026-03-05 22:41:02 -04:00
5 changed files with 126 additions and 80 deletions

View File

@@ -81,6 +81,11 @@ add_library(spinscale SHARED
src/callableTracer.cpp src/callableTracer.cpp
) )
set_target_properties(spinscale PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
# Conditionally add qutexAcquisitionHistoryTracker.cpp only when debug locks # Conditionally add qutexAcquisitionHistoryTracker.cpp only when debug locks
# are enabled, since the tracker is only referenced under CONFIG_ENABLE_DEBUG_LOCKS. # are enabled, since the tracker is only referenced under CONFIG_ENABLE_DEBUG_LOCKS.
if(ENABLE_DEBUG_LOCKS) if(ENABLE_DEBUG_LOCKS)
@@ -127,29 +132,29 @@ endif()
# Install rules # Install rules
install(TARGETS spinscale install(TARGETS spinscale
EXPORT spinscaleTargets EXPORT spinscaleTargets
LIBRARY DESTINATION lib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} NAMELINK_SKIP
ARCHIVE DESTINATION lib ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION bin RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
) )
install(DIRECTORY include/spinscale install(DIRECTORY include/spinscale
DESTINATION include DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h" FILES_MATCHING PATTERN "*.h"
) )
install(FILES include/boostAsioLinkageFix.h install(FILES include/boostAsioLinkageFix.h
DESTINATION include DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
) )
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/config.h install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/config.h
DESTINATION include DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
) )
# Install CMake config files for find_package() support # Install CMake config files for find_package() support
install(EXPORT spinscaleTargets install(EXPORT spinscaleTargets
FILE spinscaleTargets.cmake FILE spinscaleTargets.cmake
NAMESPACE spinscale:: NAMESPACE spinscale::
DESTINATION lib/cmake/spinscale DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/spinscale
) )
# Create config file for find_package() # Create config file for find_package()
@@ -158,7 +163,7 @@ include(CMakePackageConfigHelpers)
configure_package_config_file( configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/spinscaleConfig.cmake.in ${CMAKE_CURRENT_SOURCE_DIR}/cmake/spinscaleConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/spinscaleConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/spinscaleConfig.cmake
INSTALL_DESTINATION lib/cmake/spinscale INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/spinscale
) )
write_basic_package_version_file( write_basic_package_version_file(
@@ -170,5 +175,5 @@ write_basic_package_version_file(
install(FILES install(FILES
${CMAKE_CURRENT_BINARY_DIR}/spinscaleConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/spinscaleConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/spinscaleConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/spinscaleConfigVersion.cmake
DESTINATION lib/cmake/spinscale DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/spinscale
) )

View File

@@ -1,7 +1,11 @@
#ifndef ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H #ifndef ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
#define ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H #define ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
#include <functional>
#include <memory> #include <memory>
#include <optional>
#include <vector>
#include <spinscale/lockSet.h>
namespace sscl { namespace sscl {
@@ -21,10 +25,17 @@ class AsynchronousContinuationChainLink
: public std::enable_shared_from_this<AsynchronousContinuationChainLink> : public std::enable_shared_from_this<AsynchronousContinuationChainLink>
{ {
public: public:
virtual ~AsynchronousContinuationChainLink() = default; virtual ~AsynchronousContinuationChainLink() = default;
virtual std::shared_ptr<AsynchronousContinuationChainLink> virtual std::shared_ptr<AsynchronousContinuationChainLink>
getCallersContinuationShPtr() const = 0; getCallersContinuationShPtr() const = 0;
virtual std::optional<std::reference_wrapper<const LockSet>>
getLockSet() const
{ return std::nullopt; }
virtual std::optional<std::reference_wrapper<LockSet>> getLockSet()
{ return std::nullopt; }
}; };
} // namespace sscl } // namespace sscl

View File

@@ -34,7 +34,7 @@ class ComponentThread
{ {
protected: protected:
ComponentThread(ThreadId _id, std::string _name) ComponentThread(ThreadId _id, std::string _name)
: id(_id), name(std::move(_name)), work(io_service) : id(_id), name(std::move(_name)), work(io_service), keepLooping(true)
{} {}
public: public:

View File

@@ -11,15 +11,11 @@
namespace sscl { namespace sscl {
// Forward declarations
template <class OriginalCbFnT>
class SerializedAsynchronousContinuation;
class Qutex; class Qutex;
/** /**
* @brief LockSet - Manages a collection of locks for acquisition/release * @brief LockSet - Manages a collection of locks for acquisition/release
*/ */
template <class OriginalCbFnT>
class LockSet class LockSet
{ {
public: public:
@@ -44,15 +40,10 @@ public:
public: public:
/** /**
* @brief Constructor * @brief Constructor
* @param parentContinuation Reference to the parent
* SerializedAsynchronousContinuation
* @param qutexes Vector of Qutex references that must be acquired * @param qutexes Vector of Qutex references that must be acquired
*/ */
LockSet( explicit LockSet(std::vector<std::reference_wrapper<Qutex>> qutexes = {})
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation, : allLocksAcquired(false), registeredInQutexQueues(false)
std::vector<std::reference_wrapper<Qutex>> qutexes = {})
: parentContinuation(parentContinuation), allLocksAcquired(false),
registeredInQutexQueues(false)
{ {
/* Convert Qutex references to LockUsageDesc (iterators will be filled /* Convert Qutex references to LockUsageDesc (iterators will be filled
* in during registration) * in during registration)
@@ -131,7 +122,6 @@ public:
bool tryAcquireOrBackOff( bool tryAcquireOrBackOff(
LockerAndInvokerBase &lockvoker, LockerAndInvokerBase &lockvoker,
std::optional<std::reference_wrapper<Qutex>> &firstFailedQutex std::optional<std::reference_wrapper<Qutex>> &firstFailedQutex
= std::nullopt
) )
{ {
if (!registeredInQutexQueues) if (!registeredInQutexQueues)
@@ -207,15 +197,52 @@ public:
allLocksAcquired = false; allLocksAcquired = false;
} }
const LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock) const std::optional<std::reference_wrapper<LockUsageDesc>>
findLockUsageDesc(const Qutex &criterionLock)
{ {
for (auto& lockUsageDesc : locks) for (auto& lockUsageDesc : locks)
{ {
if (&lockUsageDesc.qutex.get() == &criterionLock) { if (&lockUsageDesc.qutex.get() == &criterionLock) {
return lockUsageDesc; return std::ref(lockUsageDesc);
} }
} }
return std::nullopt;
}
std::optional<std::reference_wrapper<const LockUsageDesc>>
findLockUsageDesc(const Qutex &criterionLock) const
{
for (const auto& lockUsageDesc : locks)
{
if (&lockUsageDesc.qutex.get() == &criterionLock) {
return std::cref(lockUsageDesc);
}
}
return std::nullopt;
}
LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock)
{
auto lockUsageDesc = findLockUsageDesc(criterionLock);
if (lockUsageDesc.has_value()) {
return lockUsageDesc->get();
}
// Should never happen if the LockSet is properly constructed
throw std::runtime_error(
std::string(__func__) +
": Qutex not found in this LockSet");
}
const LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock) const
{
auto lockUsageDesc = findLockUsageDesc(criterionLock);
if (lockUsageDesc.has_value()) {
return lockUsageDesc->get();
}
// Should never happen if the LockSet is properly constructed // Should never happen if the LockSet is properly constructed
throw std::runtime_error( throw std::runtime_error(
std::string(__func__) + std::string(__func__) +
@@ -235,8 +262,7 @@ public:
": LockSet::releaseQutexEarly() called but allLocksAcquired is false"); ": LockSet::releaseQutexEarly() called but allLocksAcquired is false");
} }
auto& lockUsageDesc = const_cast<LockUsageDesc&>( auto& lockUsageDesc = getLockUsageDesc(qutex);
getLockUsageDesc(qutex));
if (!lockUsageDesc.hasBeenReleased) if (!lockUsageDesc.hasBeenReleased)
{ {
@@ -251,7 +277,6 @@ public:
std::vector<LockUsageDesc> locks; std::vector<LockUsageDesc> locks;
private: private:
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation;
bool allLocksAcquired, registeredInQutexQueues; bool allLocksAcquired, registeredInQutexQueues;
}; };

View File

@@ -26,9 +26,16 @@ public:
Callback<OriginalCbFnT> originalCbFn, Callback<OriginalCbFnT> originalCbFn,
std::vector<std::reference_wrapper<Qutex>> requiredLocks) std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: PostedAsynchronousContinuation<OriginalCbFnT>(caller, originalCbFn), : PostedAsynchronousContinuation<OriginalCbFnT>(caller, originalCbFn),
requiredLocks(*this, std::move(requiredLocks)) requiredLocks(std::move(requiredLocks))
{} {}
std::optional<std::reference_wrapper<const LockSet>>
getLockSet() const override
{ return std::cref(requiredLocks); }
std::optional<std::reference_wrapper<LockSet>> getLockSet() override
{ return std::ref(requiredLocks); }
template<typename... Args> template<typename... Args>
void callOriginalCb(Args&&... args) void callOriginalCb(Args&&... args)
{ {
@@ -40,7 +47,8 @@ public:
// Return list of all qutexes in predecessors' LockSets; excludes self. // Return list of all qutexes in predecessors' LockSets; excludes self.
[[nodiscard]] [[nodiscard]]
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>> std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
getAcquiredQutexHistory() const; getAcquiredQutexHistory(
bool includeLocksWhichHaveBeenReleased = false) const;
/** /**
* @brief Release a specific qutex early * @brief Release a specific qutex early
@@ -50,7 +58,7 @@ public:
{ requiredLocks.releaseQutexEarly(qutex); } { requiredLocks.releaseQutexEarly(qutex); }
public: public:
LockSet<OriginalCbFnT> requiredLocks; LockSet requiredLocks;
std::atomic<bool> isAwakeOrBeingAwakened{false}; std::atomic<bool> isAwakeOrBeingAwakened{false};
/** /**
@@ -269,8 +277,8 @@ public:
template <class OriginalCbFnT> template <class OriginalCbFnT>
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>> std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
SerializedAsynchronousContinuation<OriginalCbFnT>::getAcquiredQutexHistory() SerializedAsynchronousContinuation<OriginalCbFnT>::getAcquiredQutexHistory(
const bool includeLocksWhichHaveBeenReleased) const
{ {
auto heldLocks = std::make_unique< auto heldLocks = std::make_unique<
std::forward_list<std::reference_wrapper<Qutex>>>(); std::forward_list<std::reference_wrapper<Qutex>>>();
@@ -284,20 +292,24 @@ const
*/ */
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin = for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
this->getCallersContinuationShPtr(); this->getCallersContinuationShPtr();
currContin != nullptr; currContin != nullptr;
currContin = currContin->getCallersContinuationShPtr()) currContin = currContin->getCallersContinuationShPtr())
{ {
auto serializedCont = std::dynamic_pointer_cast< auto heldLockSet = currContin->getLockSet();
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin); if (!heldLockSet.has_value()) { continue; }
if (serializedCont == nullptr) { continue; } // Add this continuation's locks to the held locks list
for (size_t i = 0; i < heldLockSet->get().locks.size(); ++i)
{
if (!includeLocksWhichHaveBeenReleased
&& heldLockSet->get().locks[i].hasBeenReleased)
{
continue;
}
// Add this continuation's locks to the held locks list heldLocks->push_front(heldLockSet->get().locks[i].qutex);
for (size_t i = 0; i < serializedCont->requiredLocks.locks.size(); ++i) }
{
heldLocks->push_front(serializedCont->requiredLocks.locks[i].qutex);
} }
}
return heldLocks; return heldLocks;
} }
@@ -327,24 +339,23 @@ SerializedAsynchronousContinuation<OriginalCbFnT>
currContin != nullptr; currContin != nullptr;
currContin = currContin->getCallersContinuationShPtr()) currContin = currContin->getCallersContinuationShPtr())
{ {
auto serializedCont = std::dynamic_pointer_cast< auto heldLockSet = currContin->getLockSet();
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin); if (!heldLockSet.has_value()) { continue; }
if (serializedCont == nullptr) { continue; } // A miss is expected here; only a hit indicates a potential deadlock.
auto lockUsageDesc = heldLockSet->get().findLockUsageDesc(
firstFailedQutex);
if (!lockUsageDesc.has_value()
|| lockUsageDesc->get().hasBeenReleased)
{
continue;
}
// Check if the firstFailedQutex is in this continuation's LockSet std::cout << __func__ << ":Deadlock detected: Found "
try { << "firstFailedQutex @" << &firstFailedQutex
serializedCont->requiredLocks.getLockUsageDesc(firstFailedQutex);
} catch (const std::runtime_error& e) {
std::cerr << __func__ << ": " << e.what() << std::endl;
continue;
}
std::cout << __func__ << ":Deadlock detected: Found "
<< "firstFailedQutex @" << &firstFailedQutex
<< " (" << firstFailedQutex.name << ") in LockSet of " << " (" << firstFailedQutex.name << ") in LockSet of "
<< "SerializedAsynchronousContinuation @" << "SerializedAsynchronousContinuation @"
<< serializedCont.get() << std::endl; << currContin.get() << std::endl;
return true; return true;
} }
@@ -425,33 +436,27 @@ SerializedAsynchronousContinuation<OriginalCbFnT>
*/ */
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin = for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
this->serializedContinuation.getCallersContinuationShPtr(); this->serializedContinuation.getCallersContinuationShPtr();
currContin != nullptr; currContin != nullptr;
currContin = currContin->getCallersContinuationShPtr()) currContin = currContin->getCallersContinuationShPtr())
{ {
auto serializedCont = std::dynamic_pointer_cast< auto heldLockSet = currContin->getLockSet();
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin); if (!heldLockSet.has_value()) { continue; }
if (serializedCont == nullptr) { continue; } // A miss is expected here; a hit indicates a potential gridlock.
auto lockUsageDesc = heldLockSet->get().findLockUsageDesc(
foreignLock);
if (!lockUsageDesc.has_value()) { continue; }
// Check if this continuation holds the foreign lock // Matched! We hold a lock that the foreign owner is waiting for
try { std::cout << __func__ << ": Gridlock detected: We hold lock @"
const auto& lockUsageDesc = serializedCont->requiredLocks << &foreignLock << " (" << foreignLock.name << ") in "
.getLockUsageDesc(foreignLock); "continuation @" << currContin.get()
<< ", while foreign owner @" << &foreignOwner
<< " holds lock @" << &firstFailedQutex << " ("
<< firstFailedQutex.name << ") that we're waiting for"
<< std::endl;
// Matched! We hold a lock that the foreign owner is waiting for return true;
std::cout << __func__ << ": Gridlock detected: We hold lock @"
<< &foreignLock << " (" << foreignLock.name << ") in "
"continuation @" << serializedCont.get()
<< ", while foreign owner @" << &foreignOwner
<< " holds lock @" << &firstFailedQutex << " ("
<< firstFailedQutex.name << ") that we're waiting for"
<< std::endl;
return true;
} catch (const std::runtime_error& e) {
// This continuation doesn't hold the foreign lock. Continue.
continue;
}
} }
} }