xrpld
Loading...
Searching...
No Matches
Workers.cpp
1#include <xrpl/core/detail/Workers.h>
2
3#include <xrpl/beast/core/CurrentThreadName.h>
4#include <xrpl/beast/core/LockFreeStack.h>
5#include <xrpl/core/PerfLog.h>
6
7#include <mutex>
8#include <string>
9#include <utility>
10
11namespace xrpl {
12
14 Callback& callback,
15 perf::PerfLog* perfLog,
16 std::string threadNames,
17 int numberOfThreads)
18 : callback_(callback)
19 , perfLog_(perfLog)
20 , threadNames_(std::move(threadNames))
21 , semaphore_(0)
22 , activeCount_(0)
23 , pauseCount_(0)
25{
26 setNumberOfThreads(numberOfThreads);
27}
28
35
36int
38{
39 return numberOfThreads_;
40}
41
42// VFALCO NOTE if this function is called quickly to reduce then
43// increase the number of threads, it could result in
44// more paused threads being created than expected.
45//
46void
47Workers::setNumberOfThreads(int numberOfThreads)
48{
49 static int kInstance{0};
50 if (numberOfThreads_ == numberOfThreads)
51 return;
52
53 if (perfLog_ != nullptr)
54 perfLog_->resizeJobs(numberOfThreads);
55
56 if (numberOfThreads > numberOfThreads_)
57 {
58 // Increasing the number of working threads
59 int const amount = numberOfThreads - numberOfThreads_;
60
61 for (int i = 0; i < amount; ++i)
62 {
63 // See if we can reuse a paused worker
64 Worker* worker = paused_.popFront();
65
66 if (worker != nullptr)
67 {
68 // If we got here then the worker thread is at [1]
69 // This will unblock their call to wait()
70 //
71 worker->notify();
72 }
73 else
74 {
75 worker = new Worker(*this, threadNames_, kInstance++);
76 everyone_.pushFront(worker);
77 }
78 }
79 }
80 else
81 {
82 // Decreasing the number of working threads
83 int const amount = numberOfThreads_ - numberOfThreads;
84
85 for (int i = 0; i < amount; ++i)
86 {
88
89 // Pausing a thread counts as one "internal task"
90 semaphore_.notify();
91 }
92 }
93
94 numberOfThreads_ = numberOfThreads;
95}
96
97void
99{
101
102 // Wait until all workers have paused AND no tasks are actively running.
103 // Both conditions are needed because allPaused_ (mutex-protected) and
104 // runningTaskCount_ (atomic) are not synchronized under the same lock,
105 // so allPaused_ can momentarily be true while a task is still finishing.
107 cv_.wait(lk, [this] { return allPaused_ && numberOfCurrentlyRunningTasks() == 0; });
108 lk.unlock();
109}
110
111void
113{
114 semaphore_.notify();
115}
116
117int
119{
120 return runningTaskCount_.load();
121}
122
123void
125{
126 for (;;)
127 {
128 Worker const* const worker = stack.popFront();
129
130 if (worker == nullptr)
131 break;
132
133 // This call blocks until the thread orderly exits
134 delete worker;
135 }
136}
137
138//------------------------------------------------------------------------------
139
140Workers::Worker::Worker(Workers& workers, std::string threadName, int const instance)
141 : workers_{workers}, threadName_{std::move(threadName)}, instance_{instance}
142
143{
145}
146
148{
149 {
150 std::scoped_lock const lock{mutex_};
151 ++wakeCount_;
152 shouldExit_ = true;
153 }
154
155 wakeup_.notify_one();
156 thread_.join();
157}
158
159void
161{
162 std::scoped_lock const lock{mutex_};
163 ++wakeCount_;
164 wakeup_.notify_one();
165}
166
167void
169{
170 bool shouldExit = true;
171 do
172 {
173 // Increment the count of active workers, and if
174 // we are the first one then reset the "all paused" event
175 //
176 if (++workers_.activeCount_ == 1)
177 {
178 std::scoped_lock const lk{workers_.mut_};
179 workers_.allPaused_ = false;
180 }
181
182 for (;;)
183 {
184 // Put the name back in case the callback changed it
186
187 // Acquire a task or "internal task."
188 //
189 workers_.semaphore_.wait();
190
191 // See if there's a pause request. This
192 // counts as an "internal task."
193 //
194 int pauseCount = workers_.pauseCount_.load();
195
196 if (pauseCount > 0)
197 {
198 // Try to decrement
199 pauseCount = --workers_.pauseCount_;
200
201 if (pauseCount >= 0)
202 {
203 // We got paused
204 break;
205 }
206
207 // Undo our decrement
208 ++workers_.pauseCount_;
209 }
210
211 // We couldn't pause so we must have gotten
212 // unblocked in order to process a task.
213 //
214 ++workers_.runningTaskCount_;
215 workers_.callback_.processTask(instance_);
216
217 // When the running task count drops to zero, wake stop() which
218 // may be waiting for both allPaused_ and zero running tasks.
219 // Locking mut_ before notify_all() prevents a lost wakeup:
220 // it serializes against the predicate check inside stop()'s
221 // cv_.wait(), ensuring the notification is not missed between
222 // the predicate evaluation and the actual sleep.
223 if (--workers_.runningTaskCount_ == 0)
224 {
225 std::scoped_lock const lk{workers_.mut_};
226 workers_.cv_.notify_all();
227 }
228 }
229
230 // Any worker that goes into the paused list must
231 // guarantee that it will eventually block on its
232 // event object.
233 //
234 workers_.paused_.pushFront(this);
235
236 // Decrement the count of active workers, and if we
237 // are the last one then signal the "all paused" event.
238 //
239 if (--workers_.activeCount_ == 0)
240 {
241 std::scoped_lock const lk{workers_.mut_};
242 workers_.allPaused_ = true;
243 workers_.cv_.notify_all();
244 }
245
246 // Set inactive thread name.
248
249 // [1] We will be here when the paused list is popped
250 //
251 // We block on our condition_variable, wakeup_, a requirement of being
252 // put into the paused list.
253 //
254 // wakeup_ will get signaled by either Worker::notify() or ~Worker.
255 {
257 wakeup_.wait(lock, [this] { return this->wakeCount_ > 0; });
258
259 shouldExit = shouldExit_;
260 --wakeCount_;
261 }
262 } while (!shouldExit);
263}
264
265} // namespace xrpl
Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
Element * popFront()
Pop an element off the stack.
std::string const threadName_
Definition Workers.h:189
std::thread thread_
Definition Workers.h:192
std::mutex mutex_
Definition Workers.h:193
int const instance_
Definition Workers.h:190
Worker(Workers &workers, std::string threadName, int const instance)
Definition Workers.cpp:140
std::condition_variable wakeup_
Definition Workers.h:194
void setNumberOfThreads(int numberOfThreads)
Set the desired number of threads.
Definition Workers.cpp:47
void stop()
Pause all threads and wait until they are paused.
Definition Workers.cpp:98
int numberOfThreads_
Definition Workers.h:211
std::atomic< int > activeCount_
Definition Workers.h:212
std::mutex mut_
Definition Workers.h:208
beast::LockFreeStack< Worker, PausedTag > paused_
Definition Workers.h:216
int numberOfCurrentlyRunningTasks() const noexcept
Get the number of currently executing calls of Callback::processTask.
Definition Workers.cpp:118
std::atomic< int > pauseCount_
Definition Workers.h:213
Callback & callback_
Definition Workers.h:204
void addTask()
Add a task to be performed.
Definition Workers.cpp:112
Workers(Callback &callback, perf::PerfLog *perfLog, std::string threadNames="Worker", int numberOfThreads=static_cast< int >(std::thread::hardware_concurrency()))
Create the object.
Definition Workers.cpp:13
perf::PerfLog * perfLog_
Definition Workers.h:205
static void deleteWorkers(beast::LockFreeStack< Worker > &stack)
Definition Workers.cpp:124
std::string threadNames_
Definition Workers.h:206
semaphore semaphore_
Definition Workers.h:210
bool allPaused_
Definition Workers.h:209
std::condition_variable cv_
Definition Workers.h:207
std::atomic< int > runningTaskCount_
Definition Workers.h:214
beast::LockFreeStack< Worker > everyone_
Definition Workers.h:215
int getNumberOfThreads() const noexcept
Retrieve the desired number of threads.
Definition Workers.cpp:37
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition PerfLog.h:31
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Called to perform tasks as needed.
Definition Workers.h:67
T unlock(T... args)