loadtxoutset creates a second chainstate and currently starts another full prevout-fetch pool without a test exposing the extra workers.
Could we let ChainstateManager own one pool and verify snapshot loading does not start another?
<details><summary>Details</summary>
diff --git a/src/validation.cpp b/src/validation.cpp
index 86f70bc51a..68cc4a839b 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1860,16 +1860,11 @@ CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options)
: m_dbview{std::move(db_params), std::move(options)},
m_catcherview(&m_dbview) {}
-void CoinsViews::InitCache(int32_t prevoutfetch_threads)
+void CoinsViews::InitCache(std::shared_ptr<ThreadPool> prevout_fetch_pool)
{
AssertLockHeld(::cs_main);
m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
- auto thread_pool{std::make_shared<ThreadPool>("prevout")};
- if (prevoutfetch_threads > 0) {
- thread_pool->Start(prevoutfetch_threads);
- LogInfo("Block input prevout fetching uses %d additional threads", prevoutfetch_threads);
- }
- m_connect_block_view = std::make_unique<CoinsViewOverlay>(&*m_cacheview, std::move(thread_pool));
+ m_connect_block_view = std::make_unique<CoinsViewOverlay>(&*m_cacheview, std::move(prevout_fetch_pool));
}
Chainstate::Chainstate(
@@ -1945,7 +1940,7 @@ void Chainstate::InitCoinsCache(size_t cache_size_bytes)
AssertLockHeld(::cs_main);
assert(m_coins_views != nullptr);
m_coinstip_cache_size_bytes = cache_size_bytes;
- m_coins_views->InitCache(m_chainman.m_options.prevoutfetch_threads_num);
+ m_coins_views->InitCache(m_chainman.m_prevout_fetch_pool);
}
// Lock-free: depends on `m_cached_is_ibd`, which is latched by `UpdateIBDStatus()`.
@@ -6158,8 +6153,20 @@ static ChainstateManager::Options&& Flatten(ChainstateManager::Options&& opts)
return std::move(opts);
}
+//! Create the thread pool for prefetching block input prevouts.
+static std::shared_ptr<ThreadPool> MakePrevoutFetchPool(int32_t num_threads)
+{
+ auto pool{std::make_shared<ThreadPool>("prevout")};
+ if (num_threads > 0) {
+ pool->Start(num_threads);
+ LogInfo("Block input prevout fetching uses %d additional threads", num_threads);
+ }
+ return pool;
+}
+
ChainstateManager::ChainstateManager(const util::SignalInterrupt& interrupt, Options options, node::BlockManager::Options blockman_options)
: m_script_check_queue{/*batch_size=*/128, std::clamp(options.worker_threads_num, 0, MAX_SCRIPTCHECK_THREADS)},
+ m_prevout_fetch_pool{MakePrevoutFetchPool(options.prevoutfetch_threads_num)},
m_interrupt{interrupt},
m_options{Flatten(std::move(options))},
m_blockman{interrupt, std::move(blockman_options)},
diff --git a/src/validation.h b/src/validation.h
index d89ad3539e..d2ae38ecb1 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -54,6 +54,7 @@
class Chainstate;
class CTxMemPool;
class ChainstateManager;
+class ThreadPool;
struct ChainTxData;
class DisconnectedBlockTransactions;
struct PrecomputedTransactionData;
@@ -506,7 +507,7 @@ public:
CoinsViews(DBParams db_params, CoinsViewOptions options);
//! Initialize the CCoinsViewCache member.
- void InitCache(int32_t prevoutfetch_threads) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ void InitCache(std::shared_ptr<ThreadPool> prevout_fetch_pool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
};
enum class CoinsCacheSizeState
@@ -982,6 +983,11 @@ private:
//! A queue for script verifications that have to be performed by worker threads.
CCheckQueue<CScriptCheck> m_script_check_queue;
+ //! Worker threads for prefetching block input prevouts, shared by every chainstate's
+ //! CoinsViewOverlay since only one chainstate connects blocks at a time (under cs_main).
+ //! Never null; has zero workers when prefetching is disabled.
+ const std::shared_ptr<ThreadPool> m_prevout_fetch_pool;
+
//! Timers and counters used for benchmarking validation in both background
//! and active chainstates.
SteadyClock::duration GUARDED_BY(::cs_main) time_check{};
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index 8727a9925e..ac0ffa6e3d 100755
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -524,7 +524,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info(f"Loading snapshot into second node from {dump_output['path']}")
# This node's tip is on an ancestor block of the snapshot, which should
# be the normal case
- loaded = n1.loadtxoutset(dump_output['path'])
+ prevout_pool_log = "Block input prevout fetching uses 1 additional threads"
+ with n1.assert_debug_log([], unexpected_msgs=[prevout_pool_log]):
+ loaded = n1.loadtxoutset(dump_output['path'])
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
</details>