This PR eliminates the only place that m_cs_fee_estimator
is recursively locked by refactoring out _removeTx
member function.
Related to #19303.
278@@ -267,6 +279,10 @@ class CBlockPolicyEstimator
279 unsigned int HistoricalBlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator);
280 /** Calculation of highest target that reasonable estimate can be provided for */
281 unsigned int MaxUsableEstimate() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator);
282+
283+ /** A non-thread-safe helper for the removeTx function */
284+ bool removeTxNonThreadSafeHelper(const uint256& hash, bool inBlock)
0 bool _removeTx(const uint256& hash, bool inBlock)
According to similar methods:
0 void _RelayTransaction(const uint256& txid, const uint256& wtxid)
1 EXCLUSIVE_LOCKS_REQUIRED(cs_main);
This changes removes recursion in the m_cs_fee_estimator locks.
Updated 5efec949265fd2a04f131a2a454ec984b033eb66 -> 8c277b19c8f262e550cffe263e6d910b687ac882 (pr22014.01 -> pr22014.02, diff):
561@@ -556,7 +562,8 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
562
563 bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
564 {
565- if (!removeTx(entry->GetTx().GetHash(), true)) {
566+ AssertLockHeld(m_cs_fee_estimator);
_removeTx
on the line below
Technically, it is no needed.
There are two reasons for that:
EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
will disappear (e.g., human fault) from the function declaration, the AssertLockHeld(m_cs_fee_estimator)
will emit the -Wthread-safety-analysis
warningAssertLockHeld
in the first line will guard thread safety during both compile-time and run-timeActually, I just followed a pattern from our Developer Notes.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
No conflicts as of last run.
Code-review ACK 8c277b19c8f262e550cffe263e6d910b687ac882
Carefully reviewed the m_cs_fee_estimator
lock acquires in src/policy/fees.cpp
(on master first), concluded that allowing recursion was needed due to the CBlockPolicyEstimator::removeTx(...)
method being called in CBlockPolicyEstimator::FlushUnconfirmed(...)
and CBlockPolicyEstimator::processBlockTx(...)
(in turn called by CBlockPolicyEstimator::processBlock(...)
) while holding the lock already. The PR solves this by introducing a private method _removeTx
which doesn’t take the lock, and replacing the internal calls with this. Also checked that the introduced thread safety annotations make sense.
278@@ -267,6 +279,10 @@ class CBlockPolicyEstimator
279 unsigned int HistoricalBlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator);
280 /** Calculation of highest target that reasonable estimate can be provided for */
281 unsigned int MaxUsableEstimate() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator);
282+
283+ /** A non-thread-safe helper for the removeTx function */
why is it not thread safe when it requires the caller to acquire the lock?
edit: Just a nit, so feel free to ignore.
… it requires the caller to acquire the lock
EXCLUSIVE_LOCKS_REQUIRED
just produces a warning with clang only. Or do you mean an AssertLockHeld
macro in the function body?
From my understanding, every function which requires external locking is not thread safe internally.
Anyway, what are you suggesting: to drop the comment or improve it? If the latter, how?