Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2012-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <random.h>6#include <scheduler.h>7#include <util/time.h>9#include <boost/test/unit_test.hpp>11#include <functional>12#include <mutex>13#include <thread>14#include <vector>16BOOST_AUTO_TEST_SUITE(scheduler_tests)18static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::steady_clock::time_point rescheduleTime)19{20{21std::lock_guard<std::mutex> lock(mutex);22counter += delta;23}24auto noTime = std::chrono::steady_clock::time_point::min();25if (rescheduleTime != noTime) {26CScheduler::Function f = std::bind_front(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime);27s.schedule(f, rescheduleTime);28}29}31BOOST_AUTO_TEST_CASE(manythreads)32{33// Stress test: hundreds of microsecond-scheduled tasks,34// serviced by 10 threads.35//36// So... ten shared counters, which if all the tasks execute37// properly will sum to the number of tasks done.38// Each task adds or subtracts a random amount from one of the39// counters, and then schedules another task 0-100040// microseconds in the future to subtract or add from41// the counter -random_amount+1, so in the end the shared42// counters should sum to the number of initial tasks performed.43CScheduler microTasks;45std::mutex counterMutex[10];46int counter[10] = { 0 };47FastRandomContext rng{/*fDeterministic=*/true};48auto zeroToNine = [](FastRandomContext& rc) -> int { return rc.randrange(10); }; // [0, 9]49auto randomMsec = [](FastRandomContext& rc) -> int { return -11 + (int)rc.randrange(1012); }; // [-11, 1000]50auto randomDelta = [](FastRandomContext& rc) -> int { return -1000 + (int)rc.randrange(2001); }; // [-1000, 1000]52auto start = std::chrono::steady_clock::now();53auto now = start;54std::chrono::steady_clock::time_point first, last;55size_t nTasks = microTasks.getQueueInfo(first, last);56BOOST_CHECK(nTasks == 0);58for (int i = 0; i < 100; ++i) {59auto t = now + std::chrono::microseconds(randomMsec(rng));60auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng));61int whichCounter = zeroToNine(rng);62CScheduler::Function f = std::bind_front(µTask, std::ref(microTasks),63std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]),64randomDelta(rng), tReschedule);65microTasks.schedule(f, t);66}67nTasks = microTasks.getQueueInfo(first, last);68BOOST_CHECK(nTasks == 100);69BOOST_CHECK(first < last);70BOOST_CHECK(last > now);72// As soon as these are created they will start running and servicing the queue73std::vector<std::thread> microThreads;74microThreads.reserve(10);75for (int i = 0; i < 5; i++)76microThreads.emplace_back(std::bind_front(&CScheduler::serviceQueue, µTasks));78UninterruptibleSleep(std::chrono::microseconds{600});79now = std::chrono::steady_clock::now();81// More threads and more tasks:82for (int i = 0; i < 5; i++)83microThreads.emplace_back(std::bind_front(&CScheduler::serviceQueue, µTasks));84for (int i = 0; i < 100; i++) {85auto t = now + std::chrono::microseconds(randomMsec(rng));86auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng));87int whichCounter = zeroToNine(rng);88CScheduler::Function f = std::bind_front(µTask, std::ref(microTasks),89std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]),90randomDelta(rng), tReschedule);91microTasks.schedule(f, t);92}94// Drain the task queue then exit threads95microTasks.StopWhenDrained();96// wait until all the threads are done97for (auto& thread: microThreads) {98if (thread.joinable()) thread.join();99}101int counterSum = 0;102for (int i = 0; i < 10; i++) {103BOOST_CHECK(counter[i] != 0);104counterSum += counter[i];105}106BOOST_CHECK_EQUAL(counterSum, 200);107}109BOOST_AUTO_TEST_CASE(wait_until_past)110{111std::condition_variable condvar;112Mutex mtx;113WAIT_LOCK(mtx, lock);115const auto no_wait = [&](const std::chrono::seconds& d) {116return condvar.wait_until(lock, std::chrono::steady_clock::now() - d);117};119BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::seconds{1}));120BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::minutes{1}));121BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::hours{1}));122BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::hours{10}));123BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::hours{100}));124BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::hours{1000}));125}127BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)128{129CScheduler scheduler;131// each queue should be well ordered with respect to itself but not other queues132SerialTaskRunner queue1(scheduler);133SerialTaskRunner queue2(scheduler);135// create more threads than queues136// if the queues only permit execution of one task at once then137// the extra threads should effectively be doing nothing138// if they don't we'll get out of order behaviour139std::vector<std::thread> threads;140threads.reserve(5);141for (int i = 0; i < 5; ++i) {142threads.emplace_back([&] { scheduler.serviceQueue(); });143}145// these are not atomic, if SerialTaskRunner prevents146// parallel execution at the queue level no synchronization should be required here147int counter1 = 0;148int counter2 = 0;150// just simply count up on each queue - if execution is properly ordered then151// the callbacks should run in exactly the order in which they were enqueued152for (int i = 0; i < 100; ++i) {153queue1.insert([i, &counter1]() {154bool expectation = i == counter1++;155assert(expectation);156});158queue2.insert([i, &counter2]() {159bool expectation = i == counter2++;160assert(expectation);161});162}164// finish up165scheduler.StopWhenDrained();166for (auto& thread: threads) {167if (thread.joinable()) thread.join();168}170BOOST_CHECK_EQUAL(counter1, 100);171BOOST_CHECK_EQUAL(counter2, 100);172}174BOOST_AUTO_TEST_CASE(mockforward)175{176CScheduler scheduler;178int counter{0};179CScheduler::Function dummy = [&counter]{counter++;};181// schedule jobs for 2, 5 & 8 minutes into the future183scheduler.scheduleFromNow(dummy, std::chrono::minutes{2});184scheduler.scheduleFromNow(dummy, std::chrono::minutes{5});185scheduler.scheduleFromNow(dummy, std::chrono::minutes{8});187// check taskQueue188std::chrono::steady_clock::time_point first, last;189size_t num_tasks = scheduler.getQueueInfo(first, last);190BOOST_CHECK_EQUAL(num_tasks, 3ul);192std::thread scheduler_thread([&]() { scheduler.serviceQueue(); });194// bump the scheduler forward 5 minutes195scheduler.MockForward(std::chrono::minutes{5});197// ensure scheduler has chance to process all tasks queued for before 1 ms from now.198scheduler.scheduleFromNow([&scheduler] { scheduler.stop(); }, std::chrono::milliseconds{1});199scheduler_thread.join();201// check that the queue only has one job remaining202num_tasks = scheduler.getQueueInfo(first, last);203BOOST_CHECK_EQUAL(num_tasks, 1ul);205// check that the dummy function actually ran206BOOST_CHECK_EQUAL(counter, 2);208// check that the time of the remaining job has been updated209auto now = std::chrono::steady_clock::now();210int delta = std::chrono::duration_cast<std::chrono::seconds>(first - now).count();211// should be between 2 & 3 minutes from now212BOOST_CHECK(delta > 2*60);213BOOST_CHECK(delta < 3*60);214}216BOOST_AUTO_TEST_SUITE_END()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.