Skip to content

Conversation

promag
Copy link
Contributor

@promag promag commented Sep 8, 2020

First 2 commits avoid unlock/lock mempool.cs and cs_main interchangeably by turning a loop in two loops - each mutex is locked throughout each corresponding loop.

Then explicit lock in CTxMemPool::RemoveUnbroadcastTx, CTxMemPool::GetUnbroadcastTxs and CTxMemPool::exists is removed forcing just 3 explicit WITH_LOCK where exists() is called and just 1 where GetUnbroadcastTxs() is called. This can be improved by adding an auxiliary function that locks and calls the original.

@promag
Copy link
Contributor Author

promag commented Sep 8, 2020

@hebasto this is an example of stuff that I think we can do before moving locks around. The first and second commits refactor ReattemptInitialBroadcast to drop unlock/lock in each iteration.

@promag
Copy link
Contributor Author

promag commented Sep 8, 2020

@vasild @ryanofsky your comment here would be nice too.

@hebasto
Copy link
Member

hebasto commented Sep 8, 2020

Why draft?

@promag
Copy link
Contributor Author

promag commented Sep 8, 2020

Yeah I don't mind setting it ready for review, the goal was to show an example rather than adding noise to your PR.

@promag promag marked this pull request as ready for review September 8, 2020 15:52
@hebasto
Copy link
Member

hebasto commented Sep 8, 2020

Yeah I don't mind setting it ready for review, the goal was to show an example rather than adding noise to your PR.

I'll be happy to postpone #19872 until this PR is reviewed and merged :)

Comment on lines 904 to 909
{
LOCK(cs_main);
for (const auto& elem : relay_transactions) {
RelayTransaction(elem.first, elem.second, m_connman);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8f30df2
Does this approach decrease concurrency wrt to ::cs_main uninterruptible locking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RelayTransaction doesn't hold cs_main that long.

@hebasto
Copy link
Member

hebasto commented Sep 8, 2020

Concept ACK.

@vasild
Copy link
Contributor

vasild commented Sep 8, 2020

The changes in PeerManager::ReattemptInitialBroadcast() will execute CTxMemPool::GetUnbroadcastTxs() and CTxMemPool::exists() under CTxMemPool::cs whereas previously they were not called under this mutex. Both methods acquire the mutex themselves. So this adds more recursive mutex locks.

What is the purpose of this patch? There is no description and commit messages are a bit scarce, missing answer to "Why are we doing this?".

@promag
Copy link
Contributor Author

promag commented Sep 8, 2020

@vasild sure I can detail the intention. See #19917 (comment).

@promag promag force-pushed the 2020-09-removeunbroadcasttx branch from 8f4307c to dde441c Compare September 8, 2020 21:06
@promag promag changed the title RemoveUnbroadcastTx requires mempool lock Yet another change to reduce recursive mempool locking Sep 8, 2020
@hebasto
Copy link
Member

hebasto commented Sep 9, 2020

@promag To fix TSan errors consider comparing dde441c with 049d8c5.

@@ -421,7 +421,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
for (const CTxIn& txin : it->GetTx().vin)
mapNextTx.erase(txin.prevout);

RemoveUnbroadcastTx(hash, true /* add logging because unchecked */ );
WITH_LOCK(cs, RemoveUnbroadcastTx(hash, true /* add logging because unchecked */ ));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing return?

Comment on lines 890 to 912
void PeerManager::ReattemptInitialBroadcast(CScheduler& scheduler) const
{
std::map<uint256, uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs();

for (const auto& elem : unbroadcast_txids) {
// Sanity check: all unbroadcast txns should exist in the mempool
if (m_mempool.exists(elem.first)) {
LOCK(cs_main);
std::vector<std::pair<uint256, uint256>> relay_transactions;
{
LOCK(m_mempool.cs);
std::map<uint256, uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs();
relay_transactions.reserve(unbroadcast_txids.size());
for (const auto& elem : unbroadcast_txids) {
// Sanity check: all unbroadcast txns should exist in the mempool
if (m_mempool.exists(elem.first)) {
relay_transactions.push_back(elem);
} else {
m_mempool.RemoveUnbroadcastTx(elem.first, true);
}
}
}
{
LOCK(cs_main);
for (const auto& elem : relay_transactions) {
RelayTransaction(elem.first, elem.second, m_connman);
} else {
m_mempool.RemoveUnbroadcastTx(elem.first, true);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to:

void PeerManager::ReattemptInitialBroadcast(CScheduler& scheduler) const
{
    for (const auto& elem : WITH_LOCK(m_mempool.cs, return m_mempool.GetUnbroadcastTxs())) {
        LOCK(cs_main);
        RelayTransaction(elem.first, elem.second, m_connman);
    } 

    // Schedule next run for 10-15 minutes in the future.
    ...
}

Because m_mempool.exists() will always return true for a tx returned by m_mempool.GetUnbroadcastTxs() if we don't release m_mempool.cs between the two calls.

Also, the tx could be removed after we release m_mempool.cs and before we call RelayTransaction() and this is ok and is handled just fine.

cc @gzhao408

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is it really necessary to have cs_main lock in each iteration?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it was locked in each iteration before this PR, the question should rather be "If we move LOCK(cs_main) before the loop, why would we do that?"

The frequent lock/unlock allows other threads to proceed. I don't see a reason to change it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frequent lock/unlock allows other threads to proceed. I don't see a reason to change it.

Agree (#19917 (comment)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vasild I don't agree with that. Other threads can proceed but the current thread will wait unnecessarily in each iteration for the lock and as such other things will be delayed, not mentioning the mutex overhead. See https://stackoverflow.com/a/3652428.

In this case RelayTransaction is pretty quick, nothing that can potentially cause a big lock on cs_main.

@DrahtBot
Copy link
Contributor

🐙 This pull request conflicts with the target branch and needs rebase.

Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a "draft".

@promag
Copy link
Contributor Author

promag commented Nov 7, 2020

Rebase hell.

@promag promag closed this Nov 7, 2020
@promag promag deleted the 2020-09-removeunbroadcasttx branch November 7, 2020 11:47
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants