/Users/eugenesiegel/btc/bitcoin/src/checkqueue.h
| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | // Copyright (c) 2012-2022 The Bitcoin Core developers | 
| 2 |  | // Distributed under the MIT software license, see the accompanying | 
| 3 |  | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | 
| 4 |  |  | 
| 5 |  | #ifndef BITCOIN_CHECKQUEUE_H | 
| 6 |  | #define BITCOIN_CHECKQUEUE_H | 
| 7 |  |  | 
| 8 |  | #include <logging.h> | 
| 9 |  | #include <sync.h> | 
| 10 |  | #include <tinyformat.h> | 
| 11 |  | #include <util/threadnames.h> | 
| 12 |  |  | 
| 13 |  | #include <algorithm> | 
| 14 |  | #include <iterator> | 
| 15 |  | #include <optional> | 
| 16 |  | #include <vector> | 
| 17 |  |  | 
| 18 |  | /** | 
| 19 |  |  * Queue for verifications that have to be performed. | 
| 20 |  |   * The verifications are represented by a type T, which must provide an | 
| 21 |  |   * operator(), returning an std::optional<R>. | 
| 22 |  |   * | 
| 23 |  |   * The overall result of the computation is std::nullopt if all invocations | 
| 24 |  |   * return std::nullopt, or one of the other results otherwise. | 
| 25 |  |   * | 
| 26 |  |   * One thread (the master) is assumed to push batches of verifications | 
| 27 |  |   * onto the queue, where they are processed by N-1 worker threads. When | 
| 28 |  |   * the master is done adding work, it temporarily joins the worker pool | 
| 29 |  |   * as an N'th worker, until all jobs are done. | 
| 30 |  |   * | 
| 31 |  |   */ | 
| 32 |  | template <typename T, typename R = std::remove_cvref_t<decltype(std::declval<T>()().value())>> | 
| 33 |  | class CCheckQueue | 
| 34 |  | { | 
| 35 |  | private: | 
| 36 |  |     //! Mutex to protect the inner state | 
| 37 |  |     Mutex m_mutex; | 
| 38 |  |  | 
| 39 |  |     //! Worker threads block on this when out of work | 
| 40 |  |     std::condition_variable m_worker_cv; | 
| 41 |  |  | 
| 42 |  |     //! Master thread blocks on this when out of work | 
| 43 |  |     std::condition_variable m_master_cv; | 
| 44 |  |  | 
| 45 |  |     //! The queue of elements to be processed. | 
| 46 |  |     //! As the order of booleans doesn't matter, it is used as a LIFO (stack) | 
| 47 |  |     std::vector<T> queue GUARDED_BY(m_mutex); | 
| 48 |  |  | 
| 49 |  |     //! The number of workers (including the master) that are idle. | 
| 50 |  |     int nIdle GUARDED_BY(m_mutex){0}; | 
| 51 |  |  | 
| 52 |  |     //! The total number of workers (including the master). | 
| 53 |  |     int nTotal GUARDED_BY(m_mutex){0}; | 
| 54 |  |  | 
| 55 |  |     //! The temporary evaluation result. | 
| 56 |  |     std::optional<R> m_result GUARDED_BY(m_mutex); | 
| 57 |  |  | 
| 58 |  |     /** | 
| 59 |  |      * Number of verifications that haven't completed yet. | 
| 60 |  |      * This includes elements that are no longer queued, but still in the | 
| 61 |  |      * worker's own batches. | 
| 62 |  |      */ | 
| 63 |  |     unsigned int nTodo GUARDED_BY(m_mutex){0}; | 
| 64 |  |  | 
| 65 |  |     //! The maximum number of elements to be processed in one batch | 
| 66 |  |     const unsigned int nBatchSize; | 
| 67 |  |  | 
| 68 |  |     std::vector<std::thread> m_worker_threads; | 
| 69 |  |     bool m_request_stop GUARDED_BY(m_mutex){false}; | 
| 70 |  |  | 
| 71 |  |     /** Internal function that does bulk of the verification work. If fMaster, return the final result. */ | 
| 72 |  |     std::optional<R> Loop(bool fMaster) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) | 
| 73 | 0 |     { | 
| 74 | 0 |         std::condition_variable& cond = fMaster ? m_master_cv : m_worker_cv; | 
| 75 | 0 |         std::vector<T> vChecks; | 
| 76 | 0 |         vChecks.reserve(nBatchSize); | 
| 77 | 0 |         unsigned int nNow = 0; | 
| 78 | 0 |         std::optional<R> local_result; | 
| 79 | 0 |         bool do_work; | 
| 80 | 0 |         do { | 
| 81 | 0 |             { | 
| 82 | 0 |                 WAIT_LOCK(m_mutex, lock); | Line | Count | Source |  | 265 | 0 | #define WAIT_LOCK(cs, name) UniqueLock name(LOCK_ARGS(cs)) | Line | Count | Source |  | 263 | 0 | #define LOCK_ARGS(cs) MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__ | 
 | 
 |                 WAIT_LOCK(m_mutex, lock); | Line | Count | Source |  | 265 | 0 | #define WAIT_LOCK(cs, name) UniqueLock name(LOCK_ARGS(cs)) | Line | Count | Source |  | 263 | 0 | #define LOCK_ARGS(cs) MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__ | 
 | 
 | 
| 83 |  |                 // first do the clean-up of the previous loop run (allowing us to do it in the same critsect) | 
| 84 | 0 |                 if (nNow) { | 
| 85 | 0 |                     if (local_result.has_value() && !m_result.has_value()) { | 
| 86 | 0 |                         std::swap(local_result, m_result); | 
| 87 | 0 |                     } | 
| 88 | 0 |                     nTodo -= nNow; | 
| 89 | 0 |                     if (nTodo == 0 && !fMaster) { | 
| 90 |  |                         // We processed the last element; inform the master it can exit and return the result | 
| 91 | 0 |                         m_master_cv.notify_one(); | 
| 92 | 0 |                     } | 
| 93 | 0 |                 } else { | 
| 94 |  |                     // first iteration | 
| 95 | 0 |                     nTotal++; | 
| 96 | 0 |                 } | 
| 97 |  |                 // logically, the do loop starts here | 
| 98 | 0 |                 while (queue.empty() && !m_request_stop) { | 
| 99 | 0 |                     if (fMaster && nTodo == 0) { | 
| 100 | 0 |                         nTotal--; | 
| 101 | 0 |                         std::optional<R> to_return = std::move(m_result); | 
| 102 |  |                         // reset the status for new work later | 
| 103 | 0 |                         m_result = std::nullopt; | 
| 104 |  |                         // return the current status | 
| 105 | 0 |                         return to_return; | 
| 106 | 0 |                     } | 
| 107 | 0 |                     nIdle++; | 
| 108 | 0 |                     cond.wait(lock); // wait | 
| 109 | 0 |                     nIdle--; | 
| 110 | 0 |                 } | 
| 111 | 0 |                 if (m_request_stop) { | 
| 112 |  |                     // return value does not matter, because m_request_stop is only set in the destructor. | 
| 113 | 0 |                     return std::nullopt; | 
| 114 | 0 |                 } | 
| 115 |  |  | 
| 116 |  |                 // Decide how many work units to process now. | 
| 117 |  |                 // * Do not try to do everything at once, but aim for increasingly smaller batches so | 
| 118 |  |                 //   all workers finish approximately simultaneously. | 
| 119 |  |                 // * Try to account for idle jobs which will instantly start helping. | 
| 120 |  |                 // * Don't do batches smaller than 1 (duh), or larger than nBatchSize. | 
| 121 | 0 |                 nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1))); | 
| 122 | 0 |                 auto start_it = queue.end() - nNow; | 
| 123 | 0 |                 vChecks.assign(std::make_move_iterator(start_it), std::make_move_iterator(queue.end())); | 
| 124 | 0 |                 queue.erase(start_it, queue.end()); | 
| 125 |  |                 // Check whether we need to do work at all | 
| 126 | 0 |                 do_work = !m_result.has_value(); | 
| 127 | 0 |             } | 
| 128 |  |             // execute work | 
| 129 | 0 |             if (do_work) { | 
| 130 | 0 |                 for (T& check : vChecks) { | 
| 131 | 0 |                     local_result = check(); | 
| 132 | 0 |                     if (local_result.has_value()) break; | 
| 133 | 0 |                 } | 
| 134 | 0 |             } | 
| 135 | 0 |             vChecks.clear(); | 
| 136 | 0 |         } while (true); | 
| 137 | 0 |     } Unexecuted instantiation: checkqueue.cpp:_ZN11CCheckQueueIN12_GLOBAL__N_19DumbCheckEiE4LoopEbUnexecuted instantiation: _ZN11CCheckQueueI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEE4LoopEb | 
| 138 |  |  | 
| 139 |  | public: | 
| 140 |  |     //! Mutex to ensure only one concurrent CCheckQueueControl | 
| 141 |  |     Mutex m_control_mutex; | 
| 142 |  |  | 
| 143 |  |     //! Create a new check queue | 
| 144 |  |     explicit CCheckQueue(unsigned int batch_size, int worker_threads_num) | 
| 145 | 51.2k |         : nBatchSize(batch_size) | 
| 146 | 51.2k |     { | 
| 147 | 51.2k |         LogInfo("Script verification uses %d additional threads", worker_threads_num);| Line | Count | Source |  | 356 | 0 | #define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/true, __VA_ARGS__) | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 | 
 |         LogInfo("Script verification uses %d additional threads", worker_threads_num);| Line | Count | Source |  | 356 | 51.2k | #define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/true, __VA_ARGS__) | Line | Count | Source |  | 350 | 51.2k | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 | 
 | 
| 148 | 51.2k |         m_worker_threads.reserve(worker_threads_num); | 
| 149 | 51.2k |         for (int n = 0; n < worker_threads_num; ++n0) { | 
| 150 | 0 |             m_worker_threads.emplace_back([this, n]() { | 
| 151 | 0 |                 util::ThreadRename(strprintf("scriptch.%i", n));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 |                 util::ThreadRename(strprintf("scriptch.%i", n));| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 152 | 0 |                 Loop(false /* worker thread */); | 
| 153 | 0 |             }); Unexecuted instantiation: checkqueue.cpp:_ZZN11CCheckQueueIN12_GLOBAL__N_19DumbCheckEiEC1EjiENKUlvE_clEvUnexecuted instantiation: _ZZN11CCheckQueueI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEEC1EjiENKUlvE_clEv | 
| 154 | 0 |         } | 
| 155 | 51.2k |     } Unexecuted instantiation: checkqueue.cpp:_ZN11CCheckQueueIN12_GLOBAL__N_19DumbCheckEiEC2Eji_ZN11CCheckQueueI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEEC2Eji| Line | Count | Source |  | 145 | 51.2k |         : nBatchSize(batch_size) |  | 146 | 51.2k |     { |  | 147 | 51.2k |         LogInfo("Script verification uses %d additional threads", worker_threads_num);| Line | Count | Source |  | 356 | 51.2k | #define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/true, __VA_ARGS__) | Line | Count | Source |  | 350 | 51.2k | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 | 
 |  | 148 | 51.2k |         m_worker_threads.reserve(worker_threads_num); |  | 149 | 51.2k |         for (int n = 0; n < worker_threads_num; ++n0) { |  | 150 | 0 |             m_worker_threads.emplace_back([this, n]() { |  | 151 | 0 |                 util::ThreadRename(strprintf("scriptch.%i", n)); |  | 152 | 0 |                 Loop(false /* worker thread */); |  | 153 | 0 |             }); |  | 154 | 0 |         } |  | 155 | 51.2k |     } | 
 | 
| 156 |  |  | 
| 157 |  |     // Since this class manages its own resources, which is a thread | 
| 158 |  |     // pool `m_worker_threads`, copy and move operations are not appropriate. | 
| 159 |  |     CCheckQueue(const CCheckQueue&) = delete; | 
| 160 |  |     CCheckQueue& operator=(const CCheckQueue&) = delete; | 
| 161 |  |     CCheckQueue(CCheckQueue&&) = delete; | 
| 162 |  |     CCheckQueue& operator=(CCheckQueue&&) = delete; | 
| 163 |  |  | 
| 164 |  |     //! Join the execution until completion. If at least one evaluation wasn't successful, return | 
| 165 |  |     //! its error. | 
| 166 |  |     std::optional<R> Complete() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) | 
| 167 | 0 |     { | 
| 168 | 0 |         return Loop(true /* master thread */); | 
| 169 | 0 |     } Unexecuted instantiation: checkqueue.cpp:_ZN11CCheckQueueIN12_GLOBAL__N_19DumbCheckEiE8CompleteEvUnexecuted instantiation: _ZN11CCheckQueueI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEE8CompleteEv | 
| 170 |  |  | 
| 171 |  |     //! Add a batch of checks to the queue | 
| 172 |  |     void Add(std::vector<T>&& vChecks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) | 
| 173 | 0 |     { | 
| 174 | 0 |         if (vChecks.empty()) { | 
| 175 | 0 |             return; | 
| 176 | 0 |         } | 
| 177 |  |  | 
| 178 | 0 |         { | 
| 179 | 0 |             LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 |             LOCK(m_mutex); | Line | Count | Source |  | 259 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) | Line | Count | Source |  | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) | Line | Count | Source |  | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) | Line | Count | Source |  | 8 | 0 | #define PASTE(x, y) x ## y | 
 | 
 | 
 | 
 | 
| 180 | 0 |             queue.insert(queue.end(), std::make_move_iterator(vChecks.begin()), std::make_move_iterator(vChecks.end())); | 
| 181 | 0 |             nTodo += vChecks.size(); | 
| 182 | 0 |         } | 
| 183 |  | 
 | 
| 184 | 0 |         if (vChecks.size() == 1) { | 
| 185 | 0 |             m_worker_cv.notify_one(); | 
| 186 | 0 |         } else { | 
| 187 | 0 |             m_worker_cv.notify_all(); | 
| 188 | 0 |         } | 
| 189 | 0 |     } Unexecuted instantiation: checkqueue.cpp:_ZN11CCheckQueueIN12_GLOBAL__N_19DumbCheckEiE3AddEONSt3__16vectorIS1_NS3_9allocatorIS1_EEEEUnexecuted instantiation: _ZN11CCheckQueueI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEE3AddEONS1_6vectorIS0_NS7_IS0_EEEE | 
| 190 |  |  | 
| 191 |  |     ~CCheckQueue() | 
| 192 | 51.2k |     { | 
| 193 | 51.2k |         WITH_LOCK(m_mutex, m_request_stop = true); | Line | Count | Source |  | 290 | 0 | #define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }()) | 
 |         WITH_LOCK(m_mutex, m_request_stop = true); | Line | Count | Source |  | 290 | 51.2k | #define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }()) | 
 | 
| 194 | 51.2k |         m_worker_cv.notify_all(); | 
| 195 | 51.2k |         for (std::thread& t : m_worker_threads) { | 
| 196 | 0 |             t.join(); | 
| 197 | 0 |         } | 
| 198 | 51.2k |     } Unexecuted instantiation: checkqueue.cpp:_ZN11CCheckQueueIN12_GLOBAL__N_19DumbCheckEiED2Ev_ZN11CCheckQueueI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEED2Ev| Line | Count | Source |  | 192 | 51.2k |     { |  | 193 | 51.2k |         WITH_LOCK(m_mutex, m_request_stop = true); | Line | Count | Source |  | 290 | 51.2k | #define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }()) | 
 |  | 194 | 51.2k |         m_worker_cv.notify_all(); |  | 195 | 51.2k |         for (std::thread& t : m_worker_threads) { |  | 196 | 0 |             t.join(); |  | 197 | 0 |         } |  | 198 | 51.2k |     } | 
 | 
| 199 |  |  | 
| 200 | 25.0k |     bool HasThreads() const { return !m_worker_threads.empty(); } | 
| 201 |  | }; | 
| 202 |  |  | 
| 203 |  | /** | 
| 204 |  |  * RAII-style controller object for a CCheckQueue that guarantees the passed | 
| 205 |  |  * queue is finished before continuing. | 
| 206 |  |  */ | 
| 207 |  | template <typename T, typename R = std::remove_cvref_t<decltype(std::declval<T>()().value())>> | 
| 208 |  | class SCOPED_LOCKABLE CCheckQueueControl | 
| 209 |  | { | 
| 210 |  | private: | 
| 211 |  |     CCheckQueue<T, R>& m_queue; | 
| 212 |  |     UniqueLock<Mutex> m_lock; | 
| 213 |  |     bool fDone; | 
| 214 |  |  | 
| 215 |  | public: | 
| 216 |  |     CCheckQueueControl() = delete; | 
| 217 |  |     CCheckQueueControl(const CCheckQueueControl&) = delete; | 
| 218 |  |     CCheckQueueControl& operator=(const CCheckQueueControl&) = delete; | 
| 219 | 0 |     explicit CCheckQueueControl(CCheckQueue<T>& queueIn) EXCLUSIVE_LOCK_FUNCTION(queueIn.m_control_mutex) : m_queue(queueIn), m_lock(LOCK_ARGS(queueIn.m_control_mutex)), fDone(false) {}| Line | Count | Source |  | 263 | 0 | #define LOCK_ARGS(cs) MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__ | 
 |     explicit CCheckQueueControl(CCheckQueue<T>& queueIn) EXCLUSIVE_LOCK_FUNCTION(queueIn.m_control_mutex) : m_queue(queueIn), m_lock(LOCK_ARGS(queueIn.m_control_mutex)), fDone(false) {}| Line | Count | Source |  | 263 | 0 | #define LOCK_ARGS(cs) MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__ | 
Unexecuted instantiation: checkqueue.cpp:_ZN18CCheckQueueControlIN12_GLOBAL__N_19DumbCheckEiEC2ER11CCheckQueueIS1_iEUnexecuted instantiation: _ZN18CCheckQueueControlI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEEC2ER11CCheckQueueIS0_SA_E | 
| 220 |  |  | 
| 221 |  |     std::optional<R> Complete() | 
| 222 | 0 |     { | 
| 223 | 0 |         auto ret = m_queue.Complete(); | 
| 224 | 0 |         fDone = true; | 
| 225 | 0 |         return ret; | 
| 226 | 0 |     } Unexecuted instantiation: checkqueue.cpp:_ZN18CCheckQueueControlIN12_GLOBAL__N_19DumbCheckEiE8CompleteEvUnexecuted instantiation: _ZN18CCheckQueueControlI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEE8CompleteEv | 
| 227 |  |  | 
| 228 |  |     void Add(std::vector<T>&& vChecks) | 
| 229 | 0 |     { | 
| 230 | 0 |         m_queue.Add(std::move(vChecks)); | 
| 231 | 0 |     } Unexecuted instantiation: checkqueue.cpp:_ZN18CCheckQueueControlIN12_GLOBAL__N_19DumbCheckEiE3AddEONSt3__16vectorIS1_NS3_9allocatorIS1_EEEEUnexecuted instantiation: _ZN18CCheckQueueControlI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEE3AddEONS1_6vectorIS0_NS7_IS0_EEEE | 
| 232 |  |  | 
| 233 |  |     ~CCheckQueueControl() UNLOCK_FUNCTION() | 
| 234 | 0 |     { | 
| 235 | 0 |         if (!fDone) | 
| 236 | 0 |             Complete(); | 
| 237 | 0 |     } Unexecuted instantiation: checkqueue.cpp:_ZN18CCheckQueueControlIN12_GLOBAL__N_19DumbCheckEiED2EvUnexecuted instantiation: _ZN18CCheckQueueControlI12CScriptCheckNSt3__14pairI13ScriptError_tNS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEED2Ev | 
| 238 |  | }; | 
| 239 |  |  | 
| 240 |  | #endif // BITCOIN_CHECKQUEUE_H |