Coverage Report

Created: 2026-05-30 09:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/scheduler.h
Line
Count
Source
1
// Copyright (c) 2015-present 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_SCHEDULER_H
6
#define BITCOIN_SCHEDULER_H
7
8
#include <attributes.h>
9
#include <sync.h>
10
#include <util/task_runner.h>
11
12
#include <chrono>
13
#include <condition_variable>
14
#include <cstddef>
15
#include <functional>
16
#include <list>
17
#include <map>
18
#include <thread>
19
#include <utility>
20
21
/**
22
 * Simple class for background tasks that should be run
23
 * periodically or once "after a while"
24
 *
25
 * Usage:
26
 *
27
 * CScheduler* s = new CScheduler();
28
 * s->scheduleFromNow(doSomething, std::chrono::milliseconds{11}); // Assuming a: void doSomething() { }
29
 * s->scheduleFromNow([=] { this->func(argument); }, std::chrono::milliseconds{3});
30
 * std::thread* t = new std::thread([&] { s->serviceQueue(); });
31
 *
32
 * ... then at program shutdown, make sure to call stop() to clean up the thread(s) running serviceQueue:
33
 * s->stop();
34
 * t->join();
35
 * delete t;
36
 * delete s; // Must be done after thread is interrupted/joined.
37
 */
38
class CScheduler
39
{
40
public:
41
    CScheduler();
42
    ~CScheduler();
43
44
    std::thread m_service_thread;
45
46
    typedef std::function<void()> Function;
47
48
    /** Call func at/after time t */
49
    void schedule(Function f, std::chrono::steady_clock::time_point t) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex);
50
51
    /** Call f once after the delta has passed */
52
    void scheduleFromNow(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
53
8.08k
    {
54
8.08k
        schedule(std::move(f), std::chrono::steady_clock::now() + delta);
55
8.08k
    }
56
57
    /**
58
     * Repeat f until the scheduler is stopped. First run is after delta has passed once.
59
     *
60
     * The timing is not exact: Every time f is finished, it is rescheduled to run again after delta. If you need more
61
     * accurate scheduling, don't use this method.
62
     */
63
    void scheduleEvery(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex);
64
65
    /**
66
     * Mock the scheduler to fast forward in time.
67
     * Iterates through items on taskQueue and reschedules them
68
     * to be delta_seconds sooner.
69
     */
70
    void MockForward(std::chrono::seconds delta_seconds) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex);
71
72
    /**
73
     * \anchor scheduler
74
     * Services the queue 'forever'. Should be run in a thread.
75
     */
76
    void serviceQueue() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex);
77
78
    /** Tell any threads running serviceQueue to stop as soon as the current task is done */
79
    void stop() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
80
1.30k
    {
81
1.30k
        WITH_LOCK(newTaskMutex, stopRequested = true);
82
1.30k
        newTaskScheduled.notify_all();
83
1.30k
        if (m_service_thread.joinable()) m_service_thread.join();
84
1.30k
    }
85
    /** Tell any threads running serviceQueue to stop when there is no work left to be done */
86
    void StopWhenDrained() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
87
2
    {
88
2
        WITH_LOCK(newTaskMutex, stopWhenEmpty = true);
89
2
        newTaskScheduled.notify_all();
90
2
        if (m_service_thread.joinable()) m_service_thread.join();
91
2
    }
92
93
    /**
94
     * Returns number of tasks waiting to be serviced,
95
     * and first and last task times
96
     */
97
    size_t getQueueInfo(std::chrono::steady_clock::time_point& first,
98
                        std::chrono::steady_clock::time_point& last) const
99
        EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex);
100
101
    /** Returns true if there are threads actively running in serviceQueue() */
102
    bool AreThreadsServicingQueue() const EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex);
103
104
private:
105
    mutable Mutex newTaskMutex;
106
    std::condition_variable newTaskScheduled;
107
    std::multimap<std::chrono::steady_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex);
108
    int nThreadsServicingQueue GUARDED_BY(newTaskMutex){0};
109
    bool stopRequested GUARDED_BY(newTaskMutex){false};
110
    bool stopWhenEmpty GUARDED_BY(newTaskMutex){false};
111
1.98M
    bool shouldStop() const EXCLUSIVE_LOCKS_REQUIRED(newTaskMutex) { return stopRequested || (stopWhenEmpty && taskQueue.empty()); }
112
};
113
114
/**
115
 * Class used by CScheduler clients which may schedule multiple jobs
116
 * which are required to be run serially. Jobs may not be run on the
117
 * same thread, but no two jobs will be executed
118
 * at the same time and memory will be release-acquire consistent
119
 * (the scheduler will internally do an acquire before invoking a callback
120
 * as well as a release at the end). In practice this means that a callback
121
 * B() will be able to observe all of the effects of callback A() which executed
122
 * before it.
123
 */
124
class SerialTaskRunner : public util::TaskRunnerInterface
125
{
126
private:
127
    CScheduler& m_scheduler;
128
129
    Mutex m_callbacks_mutex;
130
131
    // We are not allowed to assume the scheduler only runs in one thread,
132
    // but must ensure all callbacks happen in-order, so we end up creating
133
    // our own queue here :(
134
    std::list<std::function<void()>> m_callbacks_pending GUARDED_BY(m_callbacks_mutex);
135
    bool m_are_callbacks_running GUARDED_BY(m_callbacks_mutex) = false;
136
137
    void MaybeScheduleProcessQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex);
138
    void ProcessQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex);
139
140
public:
141
1.28k
    explicit SerialTaskRunner(CScheduler& scheduler LIFETIMEBOUND) : m_scheduler{scheduler} {}
142
143
    /**
144
     * Add a callback to be executed. Callbacks are executed serially
145
     * and memory is release-acquire consistent between callback executions.
146
     * Practically, this means that callbacks can behave as if they are executed
147
     * in order by a single thread.
148
     */
149
    void insert(std::function<void()> func) override EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex);
150
151
    /**
152
     * Processes all remaining queue members on the calling thread, blocking until queue is empty
153
     * Must be called after the CScheduler has no remaining processing threads!
154
     */
155
    void flush() override EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex);
156
157
    size_t size() override EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex);
158
};
159
160
#endif // BITCOIN_SCHEDULER_H