From 89d1a6e665e44c05621edb94b93eef879fbe391b Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 9 Oct 2025 19:10:05 -0500 Subject: [PATCH 1/9] perf: reduce timing intervals in CSigSharesManager for improved responsiveness Adjusted the timing intervals in the WorkThreadMain function of CSigSharesManager to 10 milliseconds for both message sending and work interruption checks. This change enhances the responsiveness of the signing shares manager by allowing more frequent processing of pending signature shares and message sending. This results in ~33% latency improvements in a contrived local latency functional test / benchmark from ~500ms to ~333ms --- src/llmq/signing_shares.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 1c9a911989b5c..cba3f7e34973f 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -1493,7 +1493,7 @@ void CSigSharesManager::WorkThreadMain(CConnman& connman, PeerManager& peerman) bool fMoreWork = ProcessPendingSigShares(peerman, connman); SignPendingSigShares(connman, peerman); - if (TicksSinceEpoch(SystemClock::now()) - lastSendTime > 100) { + if (TicksSinceEpoch(SystemClock::now()) - lastSendTime > 10) { SendMessages(connman); lastSendTime = TicksSinceEpoch(SystemClock::now()); } @@ -1501,7 +1501,7 @@ void CSigSharesManager::WorkThreadMain(CConnman& connman, PeerManager& peerman) Cleanup(connman); // TODO Wakeup when pending signing is needed? - if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { + if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(10))) { return; } } From 2f0312677a98b8c3846aafb9b7ca2097bafab3d1 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 15 Oct 2025 14:20:17 -0500 Subject: [PATCH 2/9] feat: enhance worker thread responsiveness in CInstantSendManager and CSigSharesManager Added a NotifyWorker function to both CInstantSendManager and CSigSharesManager to signal the worker thread when new work is available. This change improves the responsiveness of the worker threads by allowing them to wake up promptly when there are pending tasks, thus enhancing overall performance and reducing latency in processing instant send locks and signature shares. --- src/instantsend/instantsend.cpp | 21 +++++++-- src/instantsend/instantsend.h | 10 +++- src/llmq/signing.cpp | 47 +++++++++++++++++-- src/llmq/signing.h | 10 +++- src/llmq/signing_shares.cpp | 81 +++++++++++++++++++++++++++++---- src/llmq/signing_shares.h | 10 +++- 6 files changed, 157 insertions(+), 22 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index b3cb1d4b677be..6f653c1647fd2 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -163,6 +163,7 @@ MessageProcessingResult CInstantSendManager::ProcessMessage(NodeId from, std::st LOCK(cs_pendingLocks); pendingInstantSendLocks.emplace(hash, std::make_pair(from, islock)); + NotifyWorker(); return ret; } @@ -458,6 +459,7 @@ void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx) } else { RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock); } + NotifyWorker(); } void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& tx) @@ -475,6 +477,7 @@ void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& t LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- transaction %s was removed from mempool\n", __func__, tx->GetHash().ToString()); RemoveConflictingLock(::SerializeHash(*islock), *islock); + NotifyWorker(); } void CInstantSendManager::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) @@ -505,12 +508,14 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr& pb } db.WriteBlockInstantSendLocks(pblock, pindex); + NotifyWorker(); } void CInstantSendManager::BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) { db.RemoveBlockInstantSendLocks(pblock, pindexDisconnected); + NotifyWorker(); } void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) @@ -553,6 +558,7 @@ void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlock LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, pindexMined=%s\n", __func__, tx->GetHash().ToString(), pindexMined ? pindexMined->GetBlockHash().ToString() : ""); + NotifyWorker(); } void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChildren) @@ -593,6 +599,7 @@ void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChild LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, retryChildren=%d, retryChildrenCount=%d\n", __func__, txid.ToString(), retryChildren, retryChildrenCount); + NotifyWorker(); } void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx) @@ -601,6 +608,7 @@ void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx) if (auto signer = m_signer.load(std::memory_order_acquire); signer) { signer->ClearInputsFromQueue(GetIdsFromLockable(tx.vin)); } + NotifyWorker(); } void CInstantSendManager::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock) @@ -622,11 +630,13 @@ void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeI if (!pendingInstantSendLocks.count(hash)) { pendingInstantSendLocks.emplace(hash, std::make_pair(id, islock)); } + NotifyWorker(); } void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock) { HandleFullyConfirmedBlock(pindexChainLock); + NotifyWorker(); } void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) @@ -644,6 +654,7 @@ void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) if (pindex) { HandleFullyConfirmedBlock(pindex); } + NotifyWorker(); } void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) @@ -920,6 +931,7 @@ size_t CInstantSendManager::GetInstantSendLockCount() const void CInstantSendManager::WorkThreadMain(PeerManager& peerman) { while (!workInterrupt) { + uint64_t startEpoch = workEpoch.load(std::memory_order_acquire); bool fMoreWork = [&]() -> bool { if (!IsInstantSendEnabled()) return false; auto [more_work, peer_activity] = ProcessPendingInstantSendLocks(); @@ -947,10 +959,11 @@ void CInstantSendManager::WorkThreadMain(PeerManager& peerman) signer->ProcessPendingRetryLockTxs(txns); return more_work; }(); - - if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { - return; - } + if (fMoreWork) continue; + std::unique_lock l(workMutex); + workCv.wait(l, [this, startEpoch]{ + return bool(workInterrupt) || workEpoch.load(std::memory_order_acquire) != startEpoch; + }); } } diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index e9b5c809c1978..b1f7ec6290b5b 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -63,6 +63,10 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent std::thread workThread; CThreadInterrupt workInterrupt; + // Event to wake the worker thread when new work arrives + Mutex workMutex; + std::condition_variable_any workCv; + std::atomic workEpoch{0}; mutable Mutex cs_pendingLocks; // Incoming and not verified yet @@ -104,7 +108,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void Start(PeerManager& peerman); void Stop(); - void InterruptWorkerThread() { workInterrupt(); }; + void InterruptWorkerThread() { workInterrupt(); workCv.notify_all(); }; private: instantsend::PendingState ProcessPendingInstantSendLocks() @@ -133,6 +137,10 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void WorkThreadMain(PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + void NotifyWorker() { + workEpoch.fetch_add(1, std::memory_order_acq_rel); + workCv.notify_one(); + } void HandleFullyConfirmedBlock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index 55c0ab19db254..48e16b498d2cb 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -431,6 +431,8 @@ MessageProcessingResult CSigningManager::ProcessMessage(NodeId from, std::string } pendingRecoveredSigs[from].emplace_back(recoveredSig); + workEpoch.fetch_add(1, std::memory_order_acq_rel); + NotifyWorker(); return ret; } @@ -521,6 +523,13 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) const size_t nMaxBatchSize{32}; CollectPendingRecoveredSigsToVerify(nMaxBatchSize, recSigsByNode, quorums); if (recSigsByNode.empty()) { + // Check if reconstructed queue has work pending so the caller can keep looping + { + LOCK(cs_pending); + if (!pendingReconstructedRecoveredSigs.empty() || !pendingRecoveredSigs.empty()) { + return true; + } + } return false; } @@ -573,7 +582,13 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) } } - return recSigsByNode.size() >= nMaxBatchSize; + // If we still have pending items in queues, report more work to avoid sleeping + bool more_in_queues = false; + { + LOCK(cs_pending); + more_in_queues = !pendingReconstructedRecoveredSigs.empty() || !pendingRecoveredSigs.empty(); + } + return more_in_queues || recSigsByNode.size() >= nMaxBatchSize; } // signature must be verified already @@ -625,12 +640,16 @@ void CSigningManager::ProcessRecoveredSig(const std::shared_ptrGetHash().ToString()); + workEpoch.fetch_add(1, std::memory_order_acq_rel); + NotifyWorker(); } void CSigningManager::PushReconstructedRecoveredSig(const std::shared_ptr& recoveredSig) { LOCK(cs_pending); pendingReconstructedRecoveredSigs.emplace(std::piecewise_construct, std::forward_as_tuple(recoveredSig->GetHash()), std::forward_as_tuple(recoveredSig)); + workEpoch.fetch_add(1, std::memory_order_acq_rel); + NotifyWorker(); } void CSigningManager::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id) @@ -809,18 +828,38 @@ void CSigningManager::StopWorkerThread() void CSigningManager::InterruptWorkerThread() { workInterrupt(); + workCv.notify_all(); } void CSigningManager::WorkThreadMain(PeerManager& peerman) { while (!workInterrupt) { + uint64_t startEpoch = workEpoch.load(std::memory_order_acquire); bool fMoreWork = ProcessPendingRecoveredSigs(peerman); Cleanup(); - // TODO Wakeup when pending signing is needed? - if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { - return; + if (fMoreWork) continue; + std::unique_lock l(workMutex); + // Compute next cleanup deadline (~5s cadence) and wait event-driven until either + // new work arrives or the deadline is reached. + auto next_deadline = std::chrono::steady_clock::time_point::max(); + { + int64_t now_ms = TicksSinceEpoch(SystemClock::now()); + int64_t target_ms = lastCleanupTime + 5000; + if (target_ms > now_ms) { + auto delta_ms = target_ms - now_ms; + next_deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(delta_ms); + } + } + if (next_deadline == std::chrono::steady_clock::time_point::max()) { + workCv.wait(l, [this, startEpoch]{ + return bool(workInterrupt) || workEpoch.load(std::memory_order_acquire) != startEpoch; + }); + } else { + workCv.wait_until(l, next_deadline, [this, startEpoch]{ + return bool(workInterrupt) || workEpoch.load(std::memory_order_acquire) != startEpoch; + }); } } } diff --git a/src/llmq/signing.h b/src/llmq/signing.h index e49e76a143294..9d5ed7e6efc98 100644 --- a/src/llmq/signing.h +++ b/src/llmq/signing.h @@ -22,6 +22,8 @@ #include #include +#include +#include class CActiveMasternodeManager; class CChainState; @@ -236,13 +238,17 @@ class CSigningManager private: std::thread workThread; CThreadInterrupt workInterrupt; - void WorkThreadMain(PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pending, !cs_listeners); - ; + // Event to wake the worker thread when new work arrives + Mutex workMutex; + std::condition_variable_any workCv; + std::atomic workEpoch{0}; + void WorkThreadMain(PeerManager& peerman); public: void StartWorkerThread(PeerManager& peerman); void StopWorkerThread(); void InterruptWorkerThread(); + void NotifyWorker() { workEpoch.fetch_add(1, std::memory_order_acq_rel); workCv.notify_one(); } }; template diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index cba3f7e34973f..1f1a2e7ba3b9e 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -215,6 +215,8 @@ void CSigSharesManager::UnregisterAsRecoveredSigsListener() void CSigSharesManager::InterruptWorkerThread() { workInterrupt(); + // Wake the worker to allow prompt shutdown + workCv.notify_all(); } void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, const CSporkManager& sporkman, @@ -296,6 +298,9 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, return; } } + + // New inbound messages can create work (requests, shares, announcements) + NotifyWorker(); } bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSesAnn& ann) @@ -727,6 +732,7 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s return; } + bool queued_announce = false; { LOCK(cs); @@ -735,6 +741,7 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s } if (!IsAllMembersConnectedEnabled(llmqType, m_sporkman)) { sigSharesQueuedToAnnounce.Add(sigShare.GetKey(), true); + queued_announce = true; } // Update the time we've seen the last sigShare @@ -756,6 +763,10 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s } } + if (queued_announce) { + NotifyWorker(); + } + if (canTryRecovery) { TryRecoverSig(peerman, quorum, sigShare.getId(), sigShare.getMsgHash()); } @@ -1481,29 +1492,67 @@ void CSigSharesManager::BanNode(NodeId nodeId, PeerManager& peerman) }); nodeState.requestedSigShares.Clear(); nodeState.banned = true; + // Banning affects request routing and can create immediate work + NotifyWorker(); } void CSigSharesManager::WorkThreadMain(CConnman& connman, PeerManager& peerman) { - int64_t lastSendTime = 0; - while (!workInterrupt) { + // capture epoch at loop start to detect intervening notifications + uint64_t startEpoch = workEpoch.load(std::memory_order_acquire); RemoveBannedNodeStates(peerman); bool fMoreWork = ProcessPendingSigShares(peerman, connman); SignPendingSigShares(connman, peerman); - if (TicksSinceEpoch(SystemClock::now()) - lastSendTime > 10) { - SendMessages(connman); - lastSendTime = TicksSinceEpoch(SystemClock::now()); - } + bool didSend = SendMessages(connman); Cleanup(connman); - // TODO Wakeup when pending signing is needed? - if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(10))) { - return; + // If there is more work or we just sent something, iterate again without waiting + if (fMoreWork || didSend) { + continue; + } + + // Compute next wake-up deadline for periodic tasks (recovery attempts and cleanup cadence) + auto next_deadline = std::chrono::steady_clock::time_point::max(); + // Respect cleanup cadence (~5s) even when idle + { + auto now_tp = std::chrono::steady_clock::now(); + int64_t now_s = GetTime().count(); + int64_t target_s = lastCleanupTime + 5; + if (target_s > now_s) { + auto delta_ms = (target_s - now_s) * 1000; + auto cand = now_tp + std::chrono::milliseconds(delta_ms); + if (cand < next_deadline) next_deadline = cand; + } } + { + // Consider next recovery attempt times for signed sessions to avoid polling + LOCK(cs); + int64_t cur_ms = TicksSinceEpoch(SystemClock::now()); + for (const auto& [_, s] : signedSessions) { + if (s.nextAttemptTime > cur_ms) { + auto d = s.nextAttemptTime - cur_ms; + auto cand = std::chrono::steady_clock::now() + std::chrono::milliseconds(d); + if (cand < next_deadline) next_deadline = cand; + } + } + } + + // Wait event-driven until notified or deadline reached, or interrupted + std::unique_lock l(workMutex); + if (next_deadline == std::chrono::steady_clock::time_point::max()) { + workCv.wait_for(l, std::chrono::milliseconds(10), [this, startEpoch]{ + return bool(workInterrupt) || workEpoch.load(std::memory_order_acquire) != startEpoch; + }); + } else { + workCv.wait_until(l, next_deadline, [this, startEpoch]{ + return bool(workInterrupt) || workEpoch.load(std::memory_order_acquire) != startEpoch; + }); + } + // If epoch changed while we were waiting, loop will process immediately } } @@ -1511,6 +1560,8 @@ void CSigSharesManager::AsyncSign(const CQuorumCPtr& quorum, const uint256& id, { LOCK(cs_pendingSigns); pendingSigns.emplace_back(quorum, id, msgHash); + // Wake worker to handle new pending sign immediately + NotifyWorker(); } void CSigSharesManager::SignPendingSigShares(const CConnman& connman, PeerManager& peerman) @@ -1535,6 +1586,8 @@ void CSigSharesManager::SignPendingSigShares(const CConnman& connman, PeerManage } } } + // New sig shares or recovery attempts may be available + NotifyWorker(); } std::optional CSigSharesManager::CreateSigShare(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash) const @@ -1634,13 +1687,23 @@ void CSigSharesManager::ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus // we need to use a new session id as we don't know if the other node has run into a timeout already session->sendSessionId = UNINITIALIZED_SESSION_ID; } + // Wake worker so announcements are sent promptly + NotifyWorker(); } MessageProcessingResult CSigSharesManager::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) { LOCK(cs); RemoveSigSharesForSession(recoveredSig.buildSignHash().Get()); + // Cleaning up a session can free resources; wake worker to proceed + NotifyWorker(); return {}; } +void CSigSharesManager::NotifyWorker() +{ + // Avoid spurious wake-ups causing contention; simple notify is fine + workEpoch.fetch_add(1, std::memory_order_acq_rel); + workCv.notify_one(); +} } // namespace llmq diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 258a6f2417a66..a3272d7af19cb 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -26,6 +26,7 @@ #include #include #include +#include class CNode; class CConnman; @@ -379,6 +380,10 @@ class CSigSharesManager : public CRecoveredSigsListener std::thread workThread; CThreadInterrupt workInterrupt; + // Event to wake the worker thread when new work arrives + Mutex workMutex; + std::condition_variable_any workCv; + std::atomic workEpoch{0}; SigShareMap sigShares GUARDED_BY(cs); Uint256HashMap signedSessions GUARDED_BY(cs); @@ -486,8 +491,9 @@ class CSigSharesManager : public CRecoveredSigsListener void CollectSigSharesToAnnounce(const CConnman& connman, std::unordered_map>& sigSharesToAnnounce) EXCLUSIVE_LOCKS_REQUIRED(cs); - void SignPendingSigShares(const CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); - void WorkThreadMain(CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); + void SignPendingSigShares(const CConnman& connman, PeerManager& peerman); + void WorkThreadMain(CConnman& connman, PeerManager& peerman); + void NotifyWorker(); }; } // namespace llmq From dd56bbc0ee84887cc7e8ae58c07b3a8a725683cb Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 16 Oct 2025 09:00:39 -0500 Subject: [PATCH 3/9] refactor: clean up NotifyWorker function formatting in CInstantSendManager Adjusted the formatting of the NotifyWorker function in CInstantSendManager for improved readability. This change includes consistent spacing and indentation, enhancing code maintainability without altering functionality. --- src/instantsend/instantsend.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index b1f7ec6290b5b..92349cf0a4c4b 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -137,9 +137,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void WorkThreadMain(PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void NotifyWorker() { + void NotifyWorker() { workEpoch.fetch_add(1, std::memory_order_acq_rel); - workCv.notify_one(); + workCv.notify_one(); } void HandleFullyConfirmedBlock(const CBlockIndex* pindex) From 708fa0dccfe9b42152bfabde8bd4cef7d55a1b67 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 16 Oct 2025 09:01:23 -0500 Subject: [PATCH 4/9] refactor: update locking requirements for WorkThreadMain and SignPendingSigShares Modified the WorkThreadMain function in signing.h and SignPendingSigShares in signing_shares.h to specify exclusive lock requirements. This change enhances thread safety by ensuring proper locking mechanisms are in place, preventing potential race conditions during concurrent operations. --- src/llmq/signing.h | 2 +- src/llmq/signing_shares.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/llmq/signing.h b/src/llmq/signing.h index 9d5ed7e6efc98..519ea1f8f09b7 100644 --- a/src/llmq/signing.h +++ b/src/llmq/signing.h @@ -242,7 +242,7 @@ class CSigningManager Mutex workMutex; std::condition_variable_any workCv; std::atomic workEpoch{0}; - void WorkThreadMain(PeerManager& peerman); + void WorkThreadMain(PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pending, !cs_listeners); public: void StartWorkerThread(PeerManager& peerman); diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index a3272d7af19cb..24151a718fbc1 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -491,7 +491,8 @@ class CSigSharesManager : public CRecoveredSigsListener void CollectSigSharesToAnnounce(const CConnman& connman, std::unordered_map>& sigSharesToAnnounce) EXCLUSIVE_LOCKS_REQUIRED(cs); - void SignPendingSigShares(const CConnman& connman, PeerManager& peerman); + void SignPendingSigShares(const CConnman& connman, PeerManager& peerman) + EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); void WorkThreadMain(CConnman& connman, PeerManager& peerman); void NotifyWorker(); }; From 5177f80641cd348ce29a25466d90a79ef8f8cd9c Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 16 Oct 2025 13:43:08 -0500 Subject: [PATCH 5/9] refactor: update locking requirements for WorkThreadMain in signing_shares.h Added exclusive lock requirements to the WorkThreadMain function in signing_shares.h to enhance thread safety. This change ensures that the function operates correctly under concurrent conditions, preventing potential race conditions. --- src/llmq/signing_shares.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 24151a718fbc1..14ef84106b9ec 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -493,7 +493,7 @@ class CSigSharesManager : public CRecoveredSigsListener EXCLUSIVE_LOCKS_REQUIRED(cs); void SignPendingSigShares(const CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); - void WorkThreadMain(CConnman& connman, PeerManager& peerman); + void WorkThreadMain(CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); void NotifyWorker(); }; } // namespace llmq From 6df1211512e7355bbe0156017e5f55fe1b3b011f Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 17 Oct 2025 17:40:54 -0500 Subject: [PATCH 6/9] refactor: replace pair with structured PendingISLockFromPeer in CInstantSendManager Updated the CInstantSendManager to use a new struct, PendingISLockFromPeer, for better clarity and type safety. This change replaces the use of std::pair for storing node ID and InstantSendLockPtr, enhancing code readability and maintainability across multiple functions handling instant send locks. --- src/instantsend/instantsend.cpp | 28 ++++++++++++++-------------- src/instantsend/instantsend.h | 11 ++++++++--- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 6f653c1647fd2..b8d40c24f6058 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -162,7 +162,7 @@ MessageProcessingResult CInstantSendManager::ProcessMessage(NodeId from, std::st } LOCK(cs_pendingLocks); - pendingInstantSendLocks.emplace(hash, std::make_pair(from, islock)); + pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); NotifyWorker(); return ret; } @@ -234,7 +234,7 @@ instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() Uint256HashSet CInstantSendManager::ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, - const Uint256HashMap>& pend, + const Uint256HashMap& pend, std::vector>& peer_activity) { CBLSBatchVerifier batchVerifier(false, true, 8); @@ -244,8 +244,8 @@ Uint256HashSet CInstantSendManager::ProcessPendingInstantSendLocks( size_t alreadyVerified = 0; for (const auto& p : pend) { const auto& hash = p.first; - auto nodeId = p.second.first; - const auto& islock = p.second.second; + auto nodeId = p.second.node_id; + const auto& islock = p.second.islock; if (batchVerifier.badSources.count(nodeId)) { continue; @@ -316,8 +316,8 @@ Uint256HashSet CInstantSendManager::ProcessPendingInstantSendLocks( } for (const auto& p : pend) { const auto& hash = p.first; - auto nodeId = p.second.first; - const auto& islock = p.second.second; + auto nodeId = p.second.node_id; + const auto& islock = p.second.islock; if (batchVerifier.badMessages.count(hash)) { LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", @@ -394,7 +394,7 @@ MessageProcessingResult CInstantSendManager::ProcessInstantSendLock(NodeId from, } else { // put it in a separate pending map and try again later LOCK(cs_pendingLocks); - pendingNoTxInstantSendLocks.try_emplace(hash, std::make_pair(from, islock)); + pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); } // This will also add children TXs to pendingRetryTxs @@ -437,11 +437,11 @@ void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx) LOCK(cs_pendingLocks); auto it = pendingNoTxInstantSendLocks.begin(); while (it != pendingNoTxInstantSendLocks.end()) { - if (it->second.second->txid == tx->GetHash()) { + if (it->second.islock->txid == tx->GetHash()) { // we received an islock earlier LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, tx->GetHash().ToString(), it->first.ToString()); - islock = it->second.second; + islock = it->second.islock; pendingInstantSendLocks.try_emplace(it->first, it->second); pendingNoTxInstantSendLocks.erase(it); break; @@ -538,7 +538,7 @@ void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlock LOCK(cs_pendingLocks); auto it = pendingNoTxInstantSendLocks.begin(); while (it != pendingNoTxInstantSendLocks.end()) { - if (it->second.second->txid == tx->GetHash()) { + if (it->second.islock->txid == tx->GetHash()) { // we received an islock earlier, let's put it back into pending and verify/lock LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, tx->GetHash().ToString(), it->first.ToString()); @@ -628,7 +628,7 @@ void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeI if (db.KnownInstantSendLock(hash)) return; LOCK(cs_pendingLocks); if (!pendingInstantSendLocks.count(hash)) { - pendingInstantSendLocks.emplace(hash, std::make_pair(id, islock)); + pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{id, islock}); } NotifyWorker(); } @@ -853,11 +853,11 @@ bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, instants LOCK(cs_pendingLocks); auto it = pendingInstantSendLocks.find(hash); if (it != pendingInstantSendLocks.end()) { - islock = it->second.second; + islock = it->second.islock; } else { auto itNoTx = pendingNoTxInstantSendLocks.find(hash); if (itNoTx != pendingNoTxInstantSendLocks.end()) { - islock = itNoTx->second.second; + islock = itNoTx->second.islock; } else { return false; } @@ -894,7 +894,7 @@ bool CInstantSendManager::IsWaitingForTx(const uint256& txHash) const LOCK(cs_pendingLocks); auto it = pendingNoTxInstantSendLocks.begin(); while (it != pendingNoTxInstantSendLocks.end()) { - if (it->second.second->txid == txHash) { + if (it->second.islock->txid == txHash) { LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, txHash.ToString(), it->first.ToString()); return true; diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 92349cf0a4c4b..d3900a1e7ffa9 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -35,6 +35,11 @@ struct LLMQParams; namespace instantsend { class InstantSendSigner; +struct PendingISLockFromPeer { + NodeId node_id; + InstantSendLockPtr islock; +}; + struct PendingState { bool m_pending_work{false}; std::vector> m_peer_activity{}; @@ -70,9 +75,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent mutable Mutex cs_pendingLocks; // Incoming and not verified yet - Uint256HashMap> pendingInstantSendLocks GUARDED_BY(cs_pendingLocks); + Uint256HashMap pendingInstantSendLocks GUARDED_BY(cs_pendingLocks); // Tried to verify but there is no tx yet - Uint256HashMap> pendingNoTxInstantSendLocks GUARDED_BY(cs_pendingLocks); + Uint256HashMap pendingNoTxInstantSendLocks GUARDED_BY(cs_pendingLocks); // TXs which are neither IS locked nor ChainLocked. We use this to determine for which TXs we need to retry IS // locking of child TXs @@ -115,7 +120,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); Uint256HashSet ProcessPendingInstantSendLocks(const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, - const Uint256HashMap>& pend, + const Uint256HashMap& pend, std::vector>& peer_activity) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); MessageProcessingResult ProcessInstantSendLock(NodeId from, const uint256& hash, From dcd1d9a213719471ccb96778b704b6ad57bc6644 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 17 Oct 2025 17:54:17 -0500 Subject: [PATCH 7/9] perf: use vector instead of hash map for ProcessPendingInstantSendLocks The 'pend' local variable in ProcessPendingInstantSendLocks was previously using the same data structure as pendingInstantSendLocks (a hash map). However, once we're in the processing step, we only iterate sequentially through the locks - there are no hash-based lookups. This commit changes 'pend' to use std::vector for better performance: - Improved cache locality with contiguous memory layout - Better CPU prefetching during iteration (3x through the data) - Eliminates hash map overhead (bucket allocation, pointer chasing) - Filtering step uses build-new-vector approach to maintain O(n) The typical case processes up to 32 locks, making the vector's sequential access pattern ideal for modern CPU cache hierarchies. --- src/instantsend/instantsend.cpp | 22 ++++++++++++---------- src/instantsend/instantsend.h | 3 ++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index b8d40c24f6058..cc4bc01382656 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -169,7 +169,7 @@ MessageProcessingResult CInstantSendManager::ProcessMessage(NodeId from, std::st instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() { - decltype(pendingInstantSendLocks) pend; + std::vector> pend; instantsend::PendingState ret; if (!IsInstantSendEnabled()) { @@ -184,6 +184,7 @@ instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() // The keys of the removed values are temporaily stored here to avoid invalidating an iterator std::vector removed; removed.reserve(maxCount); + pend.reserve(maxCount); for (const auto& [islockHash, nodeid_islptr_pair] : pendingInstantSendLocks) { // Check if we've reached max count @@ -191,7 +192,7 @@ instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() ret.m_pending_work = true; break; } - pend.emplace(islockHash, std::move(nodeid_islptr_pair)); + pend.emplace_back(islockHash, std::move(nodeid_islptr_pair)); removed.emplace_back(islockHash); } @@ -217,16 +218,17 @@ instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() if (!badISLocks.empty()) { LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- doing verification on old active set\n", __func__); - // filter out valid IS locks from "pend" - for (auto it = pend.begin(); it != pend.end();) { - if (!badISLocks.count(it->first)) { - it = pend.erase(it); - } else { - ++it; + // filter out valid IS locks from "pend" - keep only bad ones + std::vector> filteredPend; + filteredPend.reserve(badISLocks.size()); + for (auto& p : pend) { + if (badISLocks.contains(p.first)) { + filteredPend.push_back(std::move(p)); } } + // Now check against the previous active set and perform banning if this fails - ProcessPendingInstantSendLocks(llmq_params, dkgInterval, /*ban=*/true, pend, ret.m_peer_activity); + ProcessPendingInstantSendLocks(llmq_params, dkgInterval, /*ban=*/true, filteredPend, ret.m_peer_activity); } return ret; @@ -234,7 +236,7 @@ instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() Uint256HashSet CInstantSendManager::ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, - const Uint256HashMap& pend, + const std::vector>& pend, std::vector>& peer_activity) { CBLSBatchVerifier batchVerifier(false, true, 8); diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index d3900a1e7ffa9..14b04a6786973 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -20,6 +20,7 @@ #include #include #include +#include class CBlockIndex; class CChainState; @@ -120,7 +121,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); Uint256HashSet ProcessPendingInstantSendLocks(const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, - const Uint256HashMap& pend, + const std::vector>& pend, std::vector>& peer_activity) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); MessageProcessingResult ProcessInstantSendLock(NodeId from, const uint256& hash, From ae2dc7a826e7b34b9064c8a5b76faaf86b4c6d55 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 17 Oct 2025 18:09:56 -0500 Subject: [PATCH 8/9] fix: separate mocked time from steady_clock in worker threads Fixes time-mixing bugs where mocked time (controllable in tests) was being used to compute steady_clock deadlines. Since mocked time and system time move independently, this caused incorrect wait behavior in tests. Changes: - Use steady_clock::time_point for all wait deadlines (nextAttemptTime, lastCleanupTimeSteady) - Keep mocked time (GetTime<>()) for business logic only (timeouts, session tracking) - Remove redundant workEpoch increments (NotifyWorker already does this) - Move NotifyWorker() calls to individual message handlers for better control This ensures that: 1. In production: steady_clock provides monotonic, reliable timing 2. In tests: mocked time controls business logic while steady_clock handles waits 3. No double-incrementing of workEpoch that could cause busy-wait issues --- src/llmq/signing.cpp | 37 +++++++++++------------------ src/llmq/signing.h | 1 + src/llmq/signing_shares.cpp | 46 +++++++++++++++++-------------------- src/llmq/signing_shares.h | 3 ++- 4 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index 48e16b498d2cb..05437b68869fc 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -431,7 +431,6 @@ MessageProcessingResult CSigningManager::ProcessMessage(NodeId from, std::string } pendingRecoveredSigs[from].emplace_back(recoveredSig); - workEpoch.fetch_add(1, std::memory_order_acq_rel); NotifyWorker(); return ret; } @@ -523,13 +522,9 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) const size_t nMaxBatchSize{32}; CollectPendingRecoveredSigsToVerify(nMaxBatchSize, recSigsByNode, quorums); if (recSigsByNode.empty()) { - // Check if reconstructed queue has work pending so the caller can keep looping - { - LOCK(cs_pending); - if (!pendingReconstructedRecoveredSigs.empty() || !pendingRecoveredSigs.empty()) { - return true; - } - } + // No work in this batch. Don't proactively check queues for work that may have been + // added by listeners during processing, as this causes busy-wait when combined with + // epoch changes. External threads will call NotifyWorker() to wake us if needed. return false; } @@ -582,13 +577,10 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) } } - // If we still have pending items in queues, report more work to avoid sleeping - bool more_in_queues = false; - { - LOCK(cs_pending); - more_in_queues = !pendingReconstructedRecoveredSigs.empty() || !pendingRecoveredSigs.empty(); - } - return more_in_queues || recSigsByNode.size() >= nMaxBatchSize; + // Only report more work if we processed a full batch, indicating there's likely more + // work from the original collection. Don't check queues for work added by listeners + // during processing, as that would cause busy-wait with epoch-based wake conditions. + return recSigsByNode.size() >= nMaxBatchSize; } // signature must be verified already @@ -640,15 +632,14 @@ void CSigningManager::ProcessRecoveredSig(const std::shared_ptrGetHash().ToString()); - workEpoch.fetch_add(1, std::memory_order_acq_rel); - NotifyWorker(); + // Note: Don't call NotifyWorker() here as this function is called by the worker thread itself + // NotifyWorker() is only needed when external threads add work } void CSigningManager::PushReconstructedRecoveredSig(const std::shared_ptr& recoveredSig) { LOCK(cs_pending); pendingReconstructedRecoveredSigs.emplace(std::piecewise_construct, std::forward_as_tuple(recoveredSig->GetHash()), std::forward_as_tuple(recoveredSig)); - workEpoch.fetch_add(1, std::memory_order_acq_rel); NotifyWorker(); } @@ -670,6 +661,7 @@ void CSigningManager::Cleanup() db.CleanupOldVotes(maxAge); lastCleanupTime = TicksSinceEpoch(SystemClock::now()); + lastCleanupTimeSteady = std::chrono::steady_clock::now(); } void CSigningManager::RegisterRecoveredSigsListener(CRecoveredSigsListener* l) @@ -845,11 +837,10 @@ void CSigningManager::WorkThreadMain(PeerManager& peerman) // new work arrives or the deadline is reached. auto next_deadline = std::chrono::steady_clock::time_point::max(); { - int64_t now_ms = TicksSinceEpoch(SystemClock::now()); - int64_t target_ms = lastCleanupTime + 5000; - if (target_ms > now_ms) { - auto delta_ms = target_ms - now_ms; - next_deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(delta_ms); + auto now_steady = std::chrono::steady_clock::now(); + auto next_cleanup = lastCleanupTimeSteady + std::chrono::milliseconds(5000); + if (next_cleanup > now_steady) { + next_deadline = next_cleanup; } } if (next_deadline == std::chrono::steady_clock::time_point::max()) { diff --git a/src/llmq/signing.h b/src/llmq/signing.h index 519ea1f8f09b7..5934e47ca182b 100644 --- a/src/llmq/signing.h +++ b/src/llmq/signing.h @@ -177,6 +177,7 @@ class CSigningManager FastRandomContext rnd GUARDED_BY(cs_pending); int64_t lastCleanupTime{0}; + std::chrono::steady_clock::time_point lastCleanupTimeSteady{}; mutable Mutex cs_listeners; std::vector recoveredSigsListeners GUARDED_BY(cs_listeners); diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 1f1a2e7ba3b9e..d3585f97274d5 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -239,6 +239,7 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, for (const auto& sigShare : receivedSigShares) { ProcessMessageSigShare(pfrom.GetId(), peerman, sigShare); } + NotifyWorker(); } if (msg_type == NetMsgType::QSIGSESANN) { @@ -254,6 +255,7 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, BanNode(pfrom.GetId(), peerman); return; } + NotifyWorker(); } else if (msg_type == NetMsgType::QSIGSHARESINV) { std::vector msgs; vRecv >> msgs; @@ -267,6 +269,7 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, BanNode(pfrom.GetId(), peerman); return; } + NotifyWorker(); } else if (msg_type == NetMsgType::QGETSIGSHARES) { std::vector msgs; vRecv >> msgs; @@ -280,6 +283,7 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, BanNode(pfrom.GetId(), peerman); return; } + NotifyWorker(); } else if (msg_type == NetMsgType::QBSIGSHARES) { std::vector msgs; vRecv >> msgs; @@ -297,10 +301,8 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, BanNode(pfrom.GetId(), peerman); return; } + NotifyWorker(); } - - // New inbound messages can create work (requests, shares, announcements) - NotifyWorker(); } bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSesAnn& ann) @@ -732,7 +734,6 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s return; } - bool queued_announce = false; { LOCK(cs); @@ -741,7 +742,6 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s } if (!IsAllMembersConnectedEnabled(llmqType, m_sporkman)) { sigSharesQueuedToAnnounce.Add(sigShare.GetKey(), true); - queued_announce = true; } // Update the time we've seen the last sigShare @@ -763,9 +763,9 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s } } - if (queued_announce) { - NotifyWorker(); - } + // Note: Don't call NotifyWorker() here even when queued_announce is true + // When called from worker thread: SendMessages() will handle announcements in same iteration + // When called from external thread: ProcessMessage() already calls NotifyWorker() (line 303) if (canTryRecovery) { TryRecoverSig(peerman, quorum, sigShare.getId(), sigShare.getMsgHash()); @@ -1029,7 +1029,7 @@ void CSigSharesManager::CollectSigSharesToSendConcentrated(std::unordered_map().count(); + auto curTime = std::chrono::steady_clock::now(); for (auto& [_, signedSession] : signedSessions) { if (!IsAllMembersConnectedEnabled(signedSession.quorum->params.type, m_sporkman)) { @@ -1043,7 +1043,7 @@ void CSigSharesManager::CollectSigSharesToSendConcentrated(std::unordered_map= signedSession.nextAttemptTime) { int64_t waitTime = exp2(signedSession.attempt) * EXP_SEND_FOR_RECOVERY_TIMEOUT; waitTime = std::min(MAX_SEND_FOR_RECOVERY_TIMEOUT, waitTime); - signedSession.nextAttemptTime = curTime + waitTime; + signedSession.nextAttemptTime = curTime + std::chrono::milliseconds(waitTime); auto dmn = SelectMemberForRecovery(signedSession.quorum, signedSession.sigShare.getId(), signedSession.attempt); signedSession.attempt++; @@ -1430,6 +1430,7 @@ void CSigSharesManager::Cleanup(const CConnman& connman) } lastCleanupTime = GetTime().count(); + lastCleanupTimeSteady = std::chrono::steady_clock::now(); } void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash) @@ -1519,24 +1520,19 @@ void CSigSharesManager::WorkThreadMain(CConnman& connman, PeerManager& peerman) auto next_deadline = std::chrono::steady_clock::time_point::max(); // Respect cleanup cadence (~5s) even when idle { - auto now_tp = std::chrono::steady_clock::now(); - int64_t now_s = GetTime().count(); - int64_t target_s = lastCleanupTime + 5; - if (target_s > now_s) { - auto delta_ms = (target_s - now_s) * 1000; - auto cand = now_tp + std::chrono::milliseconds(delta_ms); - if (cand < next_deadline) next_deadline = cand; + auto now_steady = std::chrono::steady_clock::now(); + auto next_cleanup = lastCleanupTimeSteady + std::chrono::seconds(5); + if (next_cleanup > now_steady) { + if (next_cleanup < next_deadline) next_deadline = next_cleanup; } } { // Consider next recovery attempt times for signed sessions to avoid polling LOCK(cs); - int64_t cur_ms = TicksSinceEpoch(SystemClock::now()); + auto now_steady = std::chrono::steady_clock::now(); for (const auto& [_, s] : signedSessions) { - if (s.nextAttemptTime > cur_ms) { - auto d = s.nextAttemptTime - cur_ms; - auto cand = std::chrono::steady_clock::now() + std::chrono::milliseconds(d); - if (cand < next_deadline) next_deadline = cand; + if (s.nextAttemptTime > now_steady) { + if (s.nextAttemptTime < next_deadline) next_deadline = s.nextAttemptTime; } } } @@ -1581,13 +1577,13 @@ void CSigSharesManager::SignPendingSigShares(const CConnman& connman, PeerManage auto& session = signedSessions[sigShare.GetSignHash()]; session.sigShare = sigShare; session.quorum = pQuorum; - session.nextAttemptTime = 0; + session.nextAttemptTime = std::chrono::steady_clock::time_point{}; session.attempt = 0; } } } - // New sig shares or recovery attempts may be available - NotifyWorker(); + // Note: Don't call NotifyWorker() here as this function is called by the worker thread itself + // NotifyWorker() is only needed when external threads add work } std::optional CSigSharesManager::CreateSigShare(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash) const diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 14ef84106b9ec..951e87b019ba2 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -355,7 +355,7 @@ class CSignedSession CSigShare sigShare; CQuorumCPtr quorum; - int64_t nextAttemptTime{0}; + std::chrono::steady_clock::time_point nextAttemptTime{}; int attempt{0}; }; @@ -414,6 +414,7 @@ class CSigSharesManager : public CRecoveredSigsListener const CSporkManager& m_sporkman; int64_t lastCleanupTime{0}; + std::chrono::steady_clock::time_point lastCleanupTimeSteady{}; std::atomic recoveredSigsCounter{0}; public: From a80a8df0cb770f462e3519e888ab70840bdab8e8 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 17 Oct 2025 18:50:15 -0500 Subject: [PATCH 9/9] refactor: optimize GetOrAdd method in signing_shares.h Replaced the previous implementation of the GetOrAdd method with a more efficient approach using emplace. This change simplifies the logic by directly attempting to insert a new entry into the internal map, improving performance and readability while maintaining the same functionality. --- src/llmq/signing_shares.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 951e87b019ba2..f73bbe8bd4418 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -210,12 +210,9 @@ class SigShareMap T& GetOrAdd(const SigShareKey& k) { - T* v = Get(k); - if (!v) { - Add(k, T()); - v = Get(k); - } - return *v; + auto& m = internalMap[k.first]; // Get or create outer map entry + auto result = m.emplace(k.second, T()); // Try to insert, returns pair + return result.first->second; // Return reference to the value (new or existing) } const T* GetFirst() const