diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index b3cb1d4b677be..cc4bc01382656 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -162,13 +162,14 @@ 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; } instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() { - decltype(pendingInstantSendLocks) pend; + std::vector> pend; instantsend::PendingState ret; if (!IsInstantSendEnabled()) { @@ -183,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 @@ -190,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); } @@ -216,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; @@ -233,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); @@ -243,8 +246,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; @@ -315,8 +318,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", @@ -393,7 +396,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 @@ -436,11 +439,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; @@ -458,6 +461,7 @@ void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx) } else { RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock); } + NotifyWorker(); } void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& tx) @@ -475,6 +479,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 +510,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) @@ -533,7 +540,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()); @@ -553,6 +560,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 +601,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 +610,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) @@ -620,13 +630,15 @@ 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(); } void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock) { HandleFullyConfirmedBlock(pindexChainLock); + NotifyWorker(); } void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) @@ -644,6 +656,7 @@ void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) if (pindex) { HandleFullyConfirmedBlock(pindex); } + NotifyWorker(); } void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) @@ -842,11 +855,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; } @@ -883,7 +896,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; @@ -920,6 +933,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 +961,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..14b04a6786973 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -20,6 +20,7 @@ #include #include #include +#include class CBlockIndex; class CChainState; @@ -35,6 +36,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{}; @@ -63,12 +69,16 @@ 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 - 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 @@ -104,14 +114,14 @@ 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() 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, @@ -133,6 +143,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..05437b68869fc 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -431,6 +431,7 @@ MessageProcessingResult CSigningManager::ProcessMessage(NodeId from, std::string } pendingRecoveredSigs[from].emplace_back(recoveredSig); + NotifyWorker(); return ret; } @@ -521,6 +522,9 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) const size_t nMaxBatchSize{32}; CollectPendingRecoveredSigsToVerify(nMaxBatchSize, recSigsByNode, quorums); if (recSigsByNode.empty()) { + // 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; } @@ -573,6 +577,9 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) } } + // 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; } @@ -625,12 +632,15 @@ void CSigningManager::ProcessRecoveredSig(const std::shared_ptrGetHash().ToString()); + // 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)); + NotifyWorker(); } void CSigningManager::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id) @@ -651,6 +661,7 @@ void CSigningManager::Cleanup() db.CleanupOldVotes(maxAge); lastCleanupTime = TicksSinceEpoch(SystemClock::now()); + lastCleanupTimeSteady = std::chrono::steady_clock::now(); } void CSigningManager::RegisterRecoveredSigsListener(CRecoveredSigsListener* l) @@ -809,18 +820,37 @@ 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(); + { + 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()) { + 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..5934e47ca182b 100644 --- a/src/llmq/signing.h +++ b/src/llmq/signing.h @@ -22,6 +22,8 @@ #include #include +#include +#include class CActiveMasternodeManager; class CChainState; @@ -175,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); @@ -236,13 +239,17 @@ class CSigningManager private: 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}; void WorkThreadMain(PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pending, !cs_listeners); - ; 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 1c9a911989b5c..d3585f97274d5 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, @@ -237,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) { @@ -252,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; @@ -265,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; @@ -278,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; @@ -295,6 +301,7 @@ void CSigSharesManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, BanNode(pfrom.GetId(), peerman); return; } + NotifyWorker(); } } @@ -756,6 +763,10 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s } } + // 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()); } @@ -1018,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)) { @@ -1032,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++; @@ -1419,6 +1430,7 @@ void CSigSharesManager::Cleanup(const CConnman& connman) } lastCleanupTime = GetTime().count(); + lastCleanupTimeSteady = std::chrono::steady_clock::now(); } void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash) @@ -1481,29 +1493,62 @@ 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 > 100) { - 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(100))) { - 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_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); + auto now_steady = std::chrono::steady_clock::now(); + for (const auto& [_, s] : signedSessions) { + if (s.nextAttemptTime > now_steady) { + if (s.nextAttemptTime < next_deadline) next_deadline = s.nextAttemptTime; + } + } } + + // 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 +1556,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) @@ -1530,11 +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; } } } + // 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 @@ -1634,13 +1683,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..f73bbe8bd4418 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; @@ -209,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 @@ -354,7 +352,7 @@ class CSignedSession CSigShare sigShare; CQuorumCPtr quorum; - int64_t nextAttemptTime{0}; + std::chrono::steady_clock::time_point nextAttemptTime{}; int attempt{0}; }; @@ -379,6 +377,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); @@ -409,6 +411,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: @@ -486,8 +489,10 @@ 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 SignPendingSigShares(const CConnman& connman, PeerManager& peerman) + EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); void WorkThreadMain(CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns); + void NotifyWorker(); }; } // namespace llmq