rippled
Loading...
Searching...
No Matches
Workers.cpp
1#include <xrpl/beast/core/CurrentThreadName.h>
2#include <xrpl/beast/utility/instrumentation.h>
3#include <xrpl/core/PerfLog.h>
4#include <xrpl/core/detail/Workers.h>
5
6namespace xrpl {
7
8Workers::Workers(Callback& callback, perf::PerfLog* perfLog, std::string const& threadNames, int numberOfThreads)
9 : m_callback(callback)
10 , perfLog_(perfLog)
11 , m_threadNames(threadNames)
12 , m_allPaused(true)
13 , m_semaphore(0)
14 , m_numberOfThreads(0)
15 , m_activeCount(0)
16 , m_pauseCount(0)
17 , m_runningTaskCount(0)
18{
19 setNumberOfThreads(numberOfThreads);
20}
21
28
29int
31{
32 return m_numberOfThreads;
33}
34
35// VFALCO NOTE if this function is called quickly to reduce then
36// increase the number of threads, it could result in
37// more paused threads being created than expected.
38//
39void
40Workers::setNumberOfThreads(int numberOfThreads)
41{
42 static int instance{0};
43 if (m_numberOfThreads == numberOfThreads)
44 return;
45
46 if (perfLog_)
47 perfLog_->resizeJobs(numberOfThreads);
48
49 if (numberOfThreads > m_numberOfThreads)
50 {
51 // Increasing the number of working threads
52 int const amount = numberOfThreads - m_numberOfThreads;
53
54 for (int i = 0; i < amount; ++i)
55 {
56 // See if we can reuse a paused worker
57 Worker* worker = m_paused.pop_front();
58
59 if (worker != nullptr)
60 {
61 // If we got here then the worker thread is at [1]
62 // This will unblock their call to wait()
63 //
64 worker->notify();
65 }
66 else
67 {
68 worker = new Worker(*this, m_threadNames, instance++);
69 m_everyone.push_front(worker);
70 }
71 }
72 }
73 else
74 {
75 // Decreasing the number of working threads
76 int const amount = m_numberOfThreads - numberOfThreads;
77
78 for (int i = 0; i < amount; ++i)
79 {
81
82 // Pausing a thread counts as one "internal task"
84 }
85 }
86
87 m_numberOfThreads = numberOfThreads;
88}
89
90void
92{
94
96 m_cv.wait(lk, [this] { return m_allPaused; });
97 lk.unlock();
98
99 XRPL_ASSERT(numberOfCurrentlyRunningTasks() == 0, "xrpl::Workers::stop : zero running tasks");
100}
101
102void
107
108int
110{
111 return m_runningTaskCount.load();
112}
113
114void
116{
117 for (;;)
118 {
119 Worker* const worker = stack.pop_front();
120
121 if (worker != nullptr)
122 {
123 // This call blocks until the thread orderly exits
124 delete worker;
125 }
126 else
127 {
128 break;
129 }
130 }
131}
132
133//------------------------------------------------------------------------------
134
135Workers::Worker::Worker(Workers& workers, std::string const& threadName, int const instance)
136 : m_workers{workers}, threadName_{threadName}, instance_{instance}, wakeCount_{0}, shouldExit_{false}
137{
139}
140
142{
143 {
144 std::lock_guard lock{mutex_};
145 ++wakeCount_;
146 shouldExit_ = true;
147 }
148
149 wakeup_.notify_one();
150 thread_.join();
151}
152
153void
155{
156 std::lock_guard lock{mutex_};
157 ++wakeCount_;
158 wakeup_.notify_one();
159}
160
161void
163{
164 bool shouldExit = true;
165 do
166 {
167 // Increment the count of active workers, and if
168 // we are the first one then reset the "all paused" event
169 //
170 if (++m_workers.m_activeCount == 1)
171 {
172 std::lock_guard lk{m_workers.m_mut};
173 m_workers.m_allPaused = false;
174 }
175
176 for (;;)
177 {
178 // Put the name back in case the callback changed it
179 beast::setCurrentThreadName(threadName_);
180
181 // Acquire a task or "internal task."
182 //
183 m_workers.m_semaphore.wait();
184
185 // See if there's a pause request. This
186 // counts as an "internal task."
187 //
188 int pauseCount = m_workers.m_pauseCount.load();
189
190 if (pauseCount > 0)
191 {
192 // Try to decrement
193 pauseCount = --m_workers.m_pauseCount;
194
195 if (pauseCount >= 0)
196 {
197 // We got paused
198 break;
199 }
200 else
201 {
202 // Undo our decrement
203 ++m_workers.m_pauseCount;
204 }
205 }
206
207 // We couldn't pause so we must have gotten
208 // unblocked in order to process a task.
209 //
210 ++m_workers.m_runningTaskCount;
211 m_workers.m_callback.processTask(instance_);
212 --m_workers.m_runningTaskCount;
213 }
214
215 // Any worker that goes into the paused list must
216 // guarantee that it will eventually block on its
217 // event object.
218 //
219 m_workers.m_paused.push_front(this);
220
221 // Decrement the count of active workers, and if we
222 // are the last one then signal the "all paused" event.
223 //
224 if (--m_workers.m_activeCount == 0)
225 {
226 std::lock_guard lk{m_workers.m_mut};
227 m_workers.m_allPaused = true;
228 m_workers.m_cv.notify_all();
229 }
230
231 // Set inactive thread name.
232 beast::setCurrentThreadName("(" + threadName_ + ")");
233
234 // [1] We will be here when the paused list is popped
235 //
236 // We block on our condition_variable, wakeup_, a requirement of being
237 // put into the paused list.
238 //
239 // wakeup_ will get signaled by either Worker::notify() or ~Worker.
240 {
241 std::unique_lock<std::mutex> lock{mutex_};
242 wakeup_.wait(lock, [this] { return this->wakeCount_ > 0; });
243
244 shouldExit = shouldExit_;
245 --wakeCount_;
246 }
247 } while (!shouldExit);
248}
249
250} // namespace xrpl
Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
Element * pop_front()
Pop an element off the stack.
std::thread thread_
Definition Workers.h:183
Worker(Workers &workers, std::string const &threadName, int const instance)
Definition Workers.cpp:135
Workers is effectively a thread pool.
Definition Workers.h:62
void setNumberOfThreads(int numberOfThreads)
Set the desired number of threads.
Definition Workers.cpp:40
Workers(Callback &callback, perf::PerfLog *perfLog, std::string const &threadNames="Worker", int numberOfThreads=static_cast< int >(std::thread::hardware_concurrency()))
Create the object.
Definition Workers.cpp:8
void stop()
Pause all threads and wait until they are paused.
Definition Workers.cpp:91
std::atomic< int > m_runningTaskCount
Definition Workers.h:205
std::mutex m_mut
Definition Workers.h:199
std::condition_variable m_cv
Definition Workers.h:198
int numberOfCurrentlyRunningTasks() const noexcept
Get the number of currently executing calls of Callback::processTask.
Definition Workers.cpp:109
bool m_allPaused
Definition Workers.h:200
void addTask()
Add a task to be performed.
Definition Workers.cpp:103
beast::LockFreeStack< Worker, PausedTag > m_paused
Definition Workers.h:207
std::atomic< int > m_pauseCount
Definition Workers.h:204
perf::PerfLog * perfLog_
Definition Workers.h:196
semaphore m_semaphore
Definition Workers.h:201
static void deleteWorkers(beast::LockFreeStack< Worker > &stack)
Definition Workers.cpp:115
int m_numberOfThreads
Definition Workers.h:202
std::string m_threadNames
Definition Workers.h:197
beast::LockFreeStack< Worker > m_everyone
Definition Workers.h:206
int getNumberOfThreads() const noexcept
Retrieve the desired number of threads.
Definition Workers.cpp:30
void notify()
Increment the count and unblock one waiting thread.
Definition semaphore.h:57
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition PerfLog.h:32
virtual void resizeJobs(int const resize)=0
Ensure enough room to store each currently executing job.
T join(T... args)
T load(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Called to perform tasks as needed.
Definition Workers.h:66