xrpld
Loading...
Searching...
No Matches
JobQueue.h
1#pragma once
2
3#include <xrpl/basics/LocalValue.h>
4#include <xrpl/core/ClosureCounter.h>
5#include <xrpl/core/JobTypeData.h>
6#include <xrpl/core/detail/Workers.h>
7#include <xrpl/json/json_value.h>
8
9// Include only the specific Boost.Coroutine2 headers actually used here.
10// Avoid `boost/coroutine2/all.hpp` because it transitively pulls in
11// `boost/context/pooled_fixedsize_stack.hpp`, whose `.malloc()` / `.free()`
12// member calls on `boost::pool` collide with MSVC's `_CRTDBG_MAP_ALLOC` macros
13// in Debug builds (see cmake/XrplCompiler.cmake).
14#include <xrpl/beast/insight/Collector.h>
15#include <xrpl/beast/insight/Gauge.h>
16#include <xrpl/beast/insight/Hook.h>
17#include <xrpl/beast/utility/Journal.h>
18#include <xrpl/core/Job.h>
19#include <xrpl/core/LoadEvent.h>
20
21#include <boost/context/protected_fixedsize_stack.hpp>
22#include <boost/coroutine2/coroutine.hpp>
23
24#include <atomic>
25#include <chrono>
26#include <condition_variable>
27#include <cstdint>
28#include <functional>
29#include <map>
30#include <memory>
31#include <mutex>
32#include <set>
33#include <string>
34#include <type_traits>
35
36namespace xrpl {
37
38namespace perf {
39class PerfLog;
40} // namespace perf
41
42class Logs;
44{
45 explicit CoroCreateT() = default;
46};
47
60{
61public:
66 {
67 private:
72 bool running_{false};
76 boost::coroutines2::coroutine<void>::push_type* yield_{};
77 boost::coroutines2::coroutine<void>::pull_type coro_;
78#ifndef NDEBUG
79 bool finished_ = false;
80#endif
81
82 public:
83 template <class F>
85
86 // Not copy-constructible or assignable
87 Coro(Coro const&) = delete;
88 Coro&
89 operator=(Coro const&) = delete;
90
92
103 void
104 yield() const;
105
120 bool
122
133 void
135
139 [[nodiscard]] bool
140 runnable() const;
141
145 void
147
151 void
153 };
154
155 using JobFunction = std::function<void()>;
156
157 JobQueue(
158 int threadCount,
159 beast::insight::Collector::ptr const& collector,
160 beast::Journal journal,
161 Logs& logs,
162 perf::PerfLog& perfLog);
163 ~JobQueue() override;
164
174 template <typename JobHandler>
175 bool
176 addJob(JobType type, std::string const& name, JobHandler&& jobHandler)
178 {
179 if (auto optionalCountedJob = jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
180 {
181 return addRefCountedJob(type, name, std::move(*optionalCountedJob));
182 }
183 return false;
184 }
185
196 template <class F>
198 postCoro(JobType t, std::string const& name, F&& f);
199
203 int
204 getJobCount(JobType t) const;
205
209 int
210 getJobCountTotal(JobType t) const;
211
215 int
216 getJobCountGE(JobType t) const;
217
222 makeLoadEvent(JobType t, std::string const& name);
223
227 void
228 addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed);
229
230 // Cannot be const because LoadMonitor has no const methods.
231 bool
232 isOverloaded();
233
234 // Cannot be const because LoadMonitor has no const methods.
236 getJson(int c = 0);
237
241 void
242 rendezvous();
243
244 void
245 stop();
246
247 bool
249 {
250 return stopping_;
251 }
252
253 // We may be able to move away from this, but we can keep it during the
254 // transition.
255 bool
256 isStopped() const;
257
258private:
259 friend class Coro;
260
262
272
273 // The number of jobs currently in processTask()
275
276 // The number of suspended coroutines
277 int nSuspend_ = 0;
278
280
281 // Statistics tracking
286
288
289 void
290 collect();
293
294 // Adds a reference counted job to the JobQueue.
295 //
296 // param type The type of job.
297 // param name Name of the job.
298 // param func std::function with signature void (Job&). Called when the
299 // job is executed.
300 //
301 // return true if func added to queue.
302 bool
303 addRefCountedJob(JobType type, std::string const& name, JobFunction const& func);
304
305 // Returns the next Job we should run now.
306 //
307 // RunnableJob:
308 // A Job in the JobSet whose slots count for its type is greater than zero.
309 //
310 // Pre-conditions:
311 // jobSet_ must not be empty.
312 // jobSet_ holds at least one RunnableJob
313 //
314 // Post-conditions:
315 // job is a valid Job object.
316 // job is removed from jobQueue_.
317 // Waiting job count of its type is decremented
318 // Running job count of its type is incremented
319 //
320 // Invariants:
321 // The calling thread owns the JobLock
322 void
323 getNextJob(Job& job);
324
325 // Indicates that a running Job has completed its task.
326 //
327 // Pre-conditions:
328 // Job must not exist in jobSet_.
329 // The JobType must not be invalid.
330 //
331 // Post-conditions:
332 // The running count of that JobType is decremented
333 // A new task is signaled if there are more waiting Jobs than the limit, if
334 // any.
335 //
336 // Invariants:
337 // <none>
338 void
339 finishJob(JobType type);
340
341 // Runs the next appropriate waiting Job.
342 //
343 // Pre-conditions:
344 // A RunnableJob must exist in the JobSet
345 //
346 // Post-conditions:
347 // The chosen RunnableJob will have Job::doJob() called.
348 //
349 // Invariants:
350 // <none>
351 void
352 processTask(int instance) override;
353
354 // Returns the limit of running jobs for the given job type.
355 // For jobs with no limit, we return the largest int. Hopefully that
356 // will be enough.
357 static int
358 getJobLimit(JobType type);
359};
360
361/*
362 An RPC command is received and is handled via ServerHandler(HTTP) or
363 Handler(websocket), depending on the connection type. The handler then calls
364 the JobQueue::postCoro() method to create a coroutine and run it at a later
365 point. This frees up the handler thread and allows it to continue handling
366 other requests while the RPC command completes its work asynchronously.
367
368 postCoro() creates a Coro object. When the Coro ctor is called, and its
369 coro_ member is initialized (a boost::coroutines::pull_type), execution
370 automatically passes to the coroutine, which we don't want at this point,
371 since we are still in the handler thread context. It's important to note
372 here that construction of a boost pull_type automatically passes execution to
373 the coroutine. A pull_type object automatically generates a push_type that is
374 passed as a parameter (do_yield) in the signature of the function the
375 pull_type was created with. This function is immediately called during coro_
376 construction and within it, Coro::yield_ is assigned the push_type
377 parameter (do_yield) address and called (yield()) so we can return execution
378 back to the caller's stack.
379
380 postCoro() then calls Coro::post(), which schedules a job on the job
381 queue to continue execution of the coroutine in a JobQueue worker thread at
382 some later time. When the job runs, we lock on the Coro::mutex_ and call
383 coro_ which continues where we had left off. Since we the last thing we did
384 in coro_ was call yield(), the next thing we continue with is calling the
385 function param f, that was passed into Coro ctor. It is within this
386 function body that the caller specifies what he would like to do while
387 running in the coroutine and allow them to suspend and resume execution.
388 A task that relies on other events to complete, such as path finding, calls
389 Coro::yield() to suspend its execution while waiting on those events to
390 complete and continue when signaled via the Coro::post() method.
391
392 There is a potential race condition that exists here where post() can get
393 called before yield() after f is called. Technically the problem only occurs
394 if the job that post() scheduled is executed before yield() is called.
395 If the post() job were to be executed before yield(), undefined behavior
396 would occur. The lock ensures that coro_ is not called again until we exit
397 the coroutine. At which point a scheduled resume() job waiting on the lock
398 would gain entry. resume() checks if the coroutine has already completed
399 (coro_ converts to false) and, if so, skips invoking operator() since
400 calling operator() on a completed boost::coroutine2 pull_type is undefined
401 behavior.
402
403 The race condition occurs as follows:
404
405 1- The coroutine is running.
406 2- The coroutine is about to suspend, but before it can do so, it must
407 arrange for some event to wake it up.
408 3- The coroutine arranges for some event to wake it up.
409 4- Before the coroutine can suspend, that event occurs and the
410 resumption of the coroutine is scheduled on the job queue. 5- Again, before
411 the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
412 Again, before the coroutine can suspend, the resumption code runs the
413 coroutine.
414 The coroutine is now running in two threads.
415
416 The lock prevents this from happening as step 6 will block until the
417 lock is released which only happens after the coroutine completes.
418*/
419
420} // namespace xrpl
421
422#include <xrpl/core/Coro.ipp> // IWYU pragma: keep
423
424namespace xrpl {
425
426template <class F>
429{
430 /* First param is a detail type to make construction private.
431 Last param is the function the coroutine runs. Signature of
432 void(std::shared_ptr<Coro>).
433 */
434 auto coro = std::make_shared<Coro>(CoroCreateT{}, *this, t, name, std::forward<F>(f));
435 if (!coro->post())
436 {
437 // The Coro was not successfully posted. Disable it so it's destructor
438 // can run with no negative side effects. Then destroy it.
439 coro->expectEarlyExit();
440 coro.reset();
441 }
442 return coro;
443}
444
445} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
std::shared_ptr< Collector > ptr
Definition Collector.h:29
A metric for measuring an integral value.
Definition Gauge.h:21
A reference to a handler for performing polled collection.
Definition Hook.h:14
Represents a JSON value.
Definition json_value.h:117
Coroutines must run to completion.
Definition JobQueue.h:66
boost::coroutines2::coroutine< void >::pull_type coro_
Definition JobQueue.h:77
Coro(Coro const &)=delete
std::mutex mutex_
Definition JobQueue.h:73
boost::coroutines2::coroutine< void >::push_type * yield_
Definition JobQueue.h:76
bool post()
Schedule coroutine execution.
bool runnable() const
Returns true if the Coro is still runnable (has not returned).
void yield() const
Suspend coroutine execution.
std::string name_
Definition JobQueue.h:71
void expectEarlyExit()
Once called, the Coro allows early exit without an assert.
std::condition_variable cv_
Definition JobQueue.h:75
Coro(CoroCreateT, JobQueue &, JobType, std::string, F &&)
void join()
Waits until coroutine returns from the user function.
Coro & operator=(Coro const &)=delete
void resume()
Resume coroutine execution.
detail::LocalValues lvs_
Definition JobQueue.h:68
std::mutex mutexRun_
Definition JobQueue.h:74
int getJobCountGE(JobType t) const
All waiting jobs at or greater than this priority.
Definition JobQueue.cpp:140
json::Value getJson(int c=0)
Definition JobQueue.cpp:186
std::function< void()> JobFunction
Definition JobQueue.h:155
void processTask(int instance) override
Perform a task.
Definition JobQueue.cpp:342
bool addJob(JobType type, std::string const &name, JobHandler &&jobHandler)
Adds a job to the JobQueue.
Definition JobQueue.h:176
JobCounter jobCounter_
Definition JobQueue.h:267
JobTypeData & getJobTypeData(JobType type)
Definition JobQueue.cpp:250
std::shared_ptr< Coro > postCoro(JobType t, std::string const &name, F &&f)
Creates a coroutine and adds a job to the queue which will run it.
Definition JobQueue.h:428
int getJobCountTotal(JobType t) const
Jobs waiting plus running at this priority.
Definition JobQueue.cpp:130
bool isStopped() const
Definition JobQueue.cpp:285
~JobQueue() override
Definition JobQueue.cpp:59
Workers workers_
Definition JobQueue.h:279
void rendezvous()
Block until no jobs running.
Definition JobQueue.cpp:243
JobQueue(int threadCount, beast::insight::Collector::ptr const &collector, beast::Journal journal, Logs &logs, perf::PerfLog &perfLog)
Definition JobQueue.cpp:24
std::atomic_bool stopping_
Definition JobQueue.h:268
int getJobCount(JobType t) const
Jobs waiting at this priority.
Definition JobQueue.cpp:120
beast::insight::Collector::ptr collector_
Definition JobQueue.h:283
bool addRefCountedJob(JobType type, std::string const &name, JobFunction const &func)
Definition JobQueue.cpp:73
bool isOverloaded()
Definition JobQueue.cpp:180
beast::Journal journal_
Definition JobQueue.h:263
JobDataMap jobData_
Definition JobQueue.h:270
std::atomic_bool stopped_
Definition JobQueue.h:269
bool isStopping() const
Definition JobQueue.h:248
beast::insight::Gauge jobCount_
Definition JobQueue.h:284
std::condition_variable cv_
Definition JobQueue.h:287
static int getJobLimit(JobType type)
Definition JobQueue.cpp:393
JobTypeData invalidJobData_
Definition JobQueue.h:271
std::mutex mutex_
Definition JobQueue.h:264
std::map< JobType, JobTypeData > JobDataMap
Definition JobQueue.h:261
beast::insight::Hook hook_
Definition JobQueue.h:285
void addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
Add multiple load events.
Definition JobQueue.cpp:169
std::unique_ptr< LoadEvent > makeLoadEvent(JobType t, std::string const &name)
Return a scoped LoadEvent.
Definition JobQueue.cpp:157
std::uint64_t lastJob_
Definition JobQueue.h:265
perf::PerfLog & perfLog_
Definition JobQueue.h:282
void finishJob(JobType type)
Definition JobQueue.cpp:321
void getNextJob(Job &job)
Definition JobQueue.cpp:291
std::set< Job > jobSet_
Definition JobQueue.h:266
Manages partitions for logging.
Definition Log.h:23
Workers is effectively a thread pool.
Definition Workers.h:61
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition PerfLog.h:31
T forward(T... args)
T is_void_v
T make_shared(T... args)
Dummy class for unit tests.
Definition Workers.h:14
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
JobType
Definition Job.h:21
ClosureCounter< void > JobCounter
Definition Job.h:141
CoroCreateT()=default
Called to perform tasks as needed.
Definition Workers.h:67