Skip to content

Conversation

@hebasto
Copy link
Member

@hebasto hebasto commented Sep 4, 2020

This is another step (after #19854) to transit CTxMemPool::cs from RecursiveMutex to Mutex.

Split out from #19306.
Thread safety annotations, lock assertions, and required explicit locking added. No behavior change.

Please note that now, since #19668 has been merged, it is safe to apply AssertLockHeld() macros as they do not swallow compile time Thread Safety Analysis warnings.

@hebasto hebasto changed the title 200904 mmx4 Avoid locking CTxMemPool::cs recursively in some cases Sep 4, 2020
@hebasto
Copy link
Member Author

hebasto commented Sep 4, 2020

cc @ajtowns @jnewbery @promag @vasild

@hebasto
Copy link
Member Author

hebasto commented Sep 4, 2020

Updated c22c4d2 -> e79bc0f (pr19872.01 -> pr19872.02, diff):

  • fixed CentOS 32-bit build on Travis CI

@DrahtBot
Copy link
Contributor

DrahtBot commented Sep 4, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@hebasto
Copy link
Member Author

hebasto commented Sep 5, 2020

Rebased e79bc0f -> 2a3e7cb (pr19872.02 -> pr19872.03) due to the conflict with #19848.

@hebasto
Copy link
Member Author

hebasto commented Sep 7, 2020

Rebased 2a3e7cb -> f594b9a (pr19872.03 -> pr19872.04) due to the conflicts with #19478 and #19556.

No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
No change in behavior, some call sites already held the lock and a lock
is added in the remaining sites.
@hebasto
Copy link
Member Author

hebasto commented Sep 7, 2020

Rebased f594b9a -> ff19c7d (pr19872.04 -> pr19872.05) due to the conflict with #19791.

Copy link
Contributor

@jnewbery jnewbery left a comment

Choose a reason for hiding this comment

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

I've reviewed the first three commits, and I'm not sure this is the right direction. Adding locks to lots of places outside the mempool seems unnecessary.

void CTxMemPool::check(const CCoinsViewCache *pcoins) const
{
LOCK(cs);
AssertLockHeld(cs);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why this change is necessary. check() is never currently called with the lock held.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is here (mutex locked a little bit earlier).

void CTxMemPool::check(const CCoinsViewCache *pcoins) const
{
LOCK(cs);
AssertLockHeld(cs);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this change is necessary. check() isn't currently called anywhere with the lock held.

LOCK(cs);
void CTxMemPool::GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const
{
AssertLockHeld(cs);
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, I don't think this change is necessary. GetTransactionAncestry() is only called with the lock held in the tests, which can be updated.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, I also saw that. I guess @hebasto preferred this approach because the changes to tests would be too big and ugly? (if we mandate that GetTransactionAncestry() will lock the mutex itself and the callers should not be holding it)

Copy link
Member Author

Choose a reason for hiding this comment

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

@vasild Correct.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

IMO we should move the lock up the stack if it prevents unnecessary unlocks/locks. That's not the case, for instance in CTxMemPool::exists. Looks like it's a premature change.

@vasild
Copy link
Contributor

vasild commented Sep 8, 2020

Concept ACK.

Similar changes are justified, assuming we want to get rid of recursive mutexes.

Recursive mutexes are used by functions that need to own a mutex to do their stuff, but some of the callers of those functions own the mutex and some don't. So the function does not have a clear interface wrt the mutex, rather it has a vague interface like "the caller may own the mutex and I will lock it myself anyway".

IMO the cleaner interface is if the mutex is acquired inside the function and none of the callers own it. If that is not possible then the function must not lock it and mandate that the callers own it. If that is too verbose for the callers, then two functions could be used - one that locks and another that does not:

funcUnprotected() EXCLUSIVE_LOCKS_REQUIRED(cs)
{
    AssertLockHeld(cs);
    ...
}

func() EXCLUSIVE_LOCKS_REQUIRED(!cs)
{
    LOCK(cs);
    funcUnprotected();
}

@hebasto
Copy link
Member Author

hebasto commented Sep 8, 2020

Many thanks to @jnewbery, @promag and @vasild for your attention to thread safety design!

Let me summarize the two approaches to preventing recursive locking of a mutex that are suggested/discussed/used in the repo:

  1. "move the lock up the stack if it prevents unnecessary unlocks/locks" (credits to @promag)
    E.g., wallet: Avoid locking cs_wallet recursively  #19833, this PR (in its current state, ff19c7d)

  2. "two functions could be used - one that locks and another that does not" (credits to @vasild)
    E.g., refactor: Make CAddrMan::cs non-recursive #19238

I believe we should reach the consensus about approach we will use while migrating from RecursiveMutex to Mutex before any further step.

Is it a topic for meeting?

@vasild
Copy link
Contributor

vasild commented Sep 8, 2020

  1. (most preferred, if possible) lock the mutex only inside the function that needs it

@hebasto
Copy link
Member Author

hebasto commented Sep 8, 2020

  1. (most preferred, if possible) lock the mutex only inside the function that needs it

Sure! I just described variants when this is not possible :)

@hebasto
Copy link
Member Author

hebasto commented Sep 8, 2020

@promag

IMO we should move the lock up the stack if it prevents unnecessary unlocks/locks. That's not the case, for instance in CTxMemPool::exists. Looks like it's a premature change.

bitcoin/src/txmempool.h

Lines 755 to 761 in 147d50d

void AddUnbroadcastTx(const uint256& txid, const uint256& wtxid) {
LOCK(cs);
// Sanity Check: the transaction should also be in the mempool
if (exists(txid)) {
m_unbroadcast_txids[txid] = wtxid;
}
}

and mempool_tests.cpp

@hebasto
Copy link
Member Author

hebasto commented Sep 8, 2020

For unit tests also there is @MarcoFalke's approach (fad3a7c5b9118d4d190401c0fbbd3e2068dffadc from #19909) to subclass CTxMemPool, and add member functions with locking required for tests.

@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".

@hebasto hebasto marked this pull request as draft November 8, 2020 20:58
@hebasto hebasto closed this Aug 24, 2021
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 24, 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