rippled
Loading...
Searching...
No Matches
JobQueue.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_CORE_JOBQUEUE_H_INCLUDED
21#define RIPPLE_CORE_JOBQUEUE_H_INCLUDED
22
23#include <xrpld/core/ClosureCounter.h>
24#include <xrpld/core/JobTypeData.h>
25#include <xrpld/core/JobTypes.h>
26#include <xrpld/core/detail/Workers.h>
27#include <xrpl/basics/LocalValue.h>
28#include <xrpl/json/json_value.h>
29
30#include <boost/coroutine/all.hpp>
31
32namespace ripple {
33
34namespace perf {
35class PerfLog;
36}
37
38class Logs;
40{
41 explicit Coro_create_t() = default;
42};
43
55{
56public:
59 {
60 private:
69 boost::coroutines::asymmetric_coroutine<void>::pull_type coro_;
70 boost::coroutines::asymmetric_coroutine<void>::push_type* yield_;
71#ifndef NDEBUG
72 bool finished_ = false;
73#endif
74
75 public:
76 // Private: Used in the implementation
77 template <class F>
79
80 // Not copy-constructible or assignable
81 Coro(Coro const&) = delete;
82 Coro&
83 operator=(Coro const&) = delete;
84
86
96 void
97 yield() const;
98
112 bool
114
124 void
126
128 bool
129 runnable() const;
130
132 void
134
136 void
138 };
139
140 using JobFunction = std::function<void()>;
141
142 JobQueue(
143 int threadCount,
144 beast::insight::Collector::ptr const& collector,
145 beast::Journal journal,
146 Logs& logs,
147 perf::PerfLog& perfLog);
148 ~JobQueue();
149
159 template <
160 typename JobHandler,
162 decltype(std::declval<JobHandler&&>()()),
163 void>::value>>
164 bool
165 addJob(JobType type, std::string const& name, JobHandler&& jobHandler)
166 {
167 if (auto optionalCountedJob =
168 jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
169 {
170 return addRefCountedJob(type, name, std::move(*optionalCountedJob));
171 }
172 return false;
173 }
174
184 template <class F>
186 postCoro(JobType t, std::string const& name, F&& f);
187
190 int
191 getJobCount(JobType t) const;
192
195 int
196 getJobCountTotal(JobType t) const;
197
200 int
201 getJobCountGE(JobType t) const;
202
206 makeLoadEvent(JobType t, std::string const& name);
207
210 void
211 addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed);
212
213 // Cannot be const because LoadMonitor has no const methods.
214 bool
215 isOverloaded();
216
217 // Cannot be const because LoadMonitor has no const methods.
219 getJson(int c = 0);
220
222 void
223 rendezvous();
224
225 void
226 stop();
227
228 bool
230 {
231 return stopping_;
232 }
233
234 // We may be able to move away from this, but we can keep it during the
235 // transition.
236 bool
237 isStopped() const;
238
239private:
240 friend class Coro;
241
243
253
254 // The number of jobs currently in processTask()
256
257 // The number of suspended coroutines
258 int nSuspend_ = 0;
259
261
262 // Statistics tracking
267
269
270 void
271 collect();
274
275 // Adds a reference counted job to the JobQueue.
276 //
277 // param type The type of job.
278 // param name Name of the job.
279 // param func std::function with signature void (Job&). Called when the
280 // job is executed.
281 //
282 // return true if func added to queue.
283 bool
285 JobType type,
286 std::string const& name,
287 JobFunction const& func);
288
289 // Returns the next Job we should run now.
290 //
291 // RunnableJob:
292 // A Job in the JobSet whose slots count for its type is greater than zero.
293 //
294 // Pre-conditions:
295 // mJobSet must not be empty.
296 // mJobSet holds at least one RunnableJob
297 //
298 // Post-conditions:
299 // job is a valid Job object.
300 // job is removed from mJobQueue.
301 // Waiting job count of its type is decremented
302 // Running job count of its type is incremented
303 //
304 // Invariants:
305 // The calling thread owns the JobLock
306 void
307 getNextJob(Job& job);
308
309 // Indicates that a running Job has completed its task.
310 //
311 // Pre-conditions:
312 // Job must not exist in mJobSet.
313 // The JobType must not be invalid.
314 //
315 // Post-conditions:
316 // The running count of that JobType is decremented
317 // A new task is signaled if there are more waiting Jobs than the limit, if
318 // any.
319 //
320 // Invariants:
321 // <none>
322 void
323 finishJob(JobType type);
324
325 // Runs the next appropriate waiting Job.
326 //
327 // Pre-conditions:
328 // A RunnableJob must exist in the JobSet
329 //
330 // Post-conditions:
331 // The chosen RunnableJob will have Job::doJob() called.
332 //
333 // Invariants:
334 // <none>
335 void
336 processTask(int instance) override;
337
338 // Returns the limit of running jobs for the given job type.
339 // For jobs with no limit, we return the largest int. Hopefully that
340 // will be enough.
341 int
342 getJobLimit(JobType type);
343};
344
345/*
346 An RPC command is received and is handled via ServerHandler(HTTP) or
347 Handler(websocket), depending on the connection type. The handler then calls
348 the JobQueue::postCoro() method to create a coroutine and run it at a later
349 point. This frees up the handler thread and allows it to continue handling
350 other requests while the RPC command completes its work asynchronously.
351
352 postCoro() creates a Coro object. When the Coro ctor is called, and its
353 coro_ member is initialized (a boost::coroutines::pull_type), execution
354 automatically passes to the coroutine, which we don't want at this point,
355 since we are still in the handler thread context. It's important to note
356 here that construction of a boost pull_type automatically passes execution to
357 the coroutine. A pull_type object automatically generates a push_type that is
358 passed as a parameter (do_yield) in the signature of the function the
359 pull_type was created with. This function is immediately called during coro_
360 construction and within it, Coro::yield_ is assigned the push_type
361 parameter (do_yield) address and called (yield()) so we can return execution
362 back to the caller's stack.
363
364 postCoro() then calls Coro::post(), which schedules a job on the job
365 queue to continue execution of the coroutine in a JobQueue worker thread at
366 some later time. When the job runs, we lock on the Coro::mutex_ and call
367 coro_ which continues where we had left off. Since we the last thing we did
368 in coro_ was call yield(), the next thing we continue with is calling the
369 function param f, that was passed into Coro ctor. It is within this
370 function body that the caller specifies what he would like to do while
371 running in the coroutine and allow them to suspend and resume execution.
372 A task that relies on other events to complete, such as path finding, calls
373 Coro::yield() to suspend its execution while waiting on those events to
374 complete and continue when signaled via the Coro::post() method.
375
376 There is a potential race condition that exists here where post() can get
377 called before yield() after f is called. Technically the problem only occurs
378 if the job that post() scheduled is executed before yield() is called.
379 If the post() job were to be executed before yield(), undefined behavior
380 would occur. The lock ensures that coro_ is not called again until we exit
381 the coroutine. At which point a scheduled resume() job waiting on the lock
382 would gain entry, harmlessly call coro_ and immediately return as we have
383 already completed the coroutine.
384
385 The race condition occurs as follows:
386
387 1- The coroutine is running.
388 2- The coroutine is about to suspend, but before it can do so, it must
389 arrange for some event to wake it up.
390 3- The coroutine arranges for some event to wake it up.
391 4- Before the coroutine can suspend, that event occurs and the
392 resumption of the coroutine is scheduled on the job queue. 5- Again, before
393 the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
394 Again, before the coroutine can suspend, the resumption code runs the
395 coroutine.
396 The coroutine is now running in two threads.
397
398 The lock prevents this from happening as step 6 will block until the
399 lock is released which only happens after the coroutine completes.
400*/
401
402} // namespace ripple
403
404#include <xrpld/core/Coro.ipp>
405
406namespace ripple {
407
408template <class F>
411{
412 /* First param is a detail type to make construction private.
413 Last param is the function the coroutine runs. Signature of
414 void(std::shared_ptr<Coro>).
415 */
416 auto coro = std::make_shared<Coro>(
417 Coro_create_t{}, *this, t, name, std::forward<F>(f));
418 if (!coro->post())
419 {
420 // The Coro was not successfully posted. Disable it so it's destructor
421 // can run with no negative side effects. Then destroy it.
422 coro->expectEarlyExit();
423 coro.reset();
424 }
425 return coro;
426}
427
428} // namespace ripple
429
430#endif
Represents a JSON value.
Definition: json_value.h:148
A generic endpoint for log messages.
Definition: Journal.h:60
A metric for measuring an integral value.
Definition: Gauge.h:40
A reference to a handler for performing polled collection.
Definition: Hook.h:32
std::optional< Substitute< Closure > > wrap(Closure &&closure)
Wrap the passed closure with a reference counter.
Coroutines must run to completion.
Definition: JobQueue.h:59
void join()
Waits until coroutine returns from the user function.
std::string name_
Definition: JobQueue.h:64
std::mutex mutex_run_
Definition: JobQueue.h:67
void resume()
Resume coroutine execution.
std::condition_variable cv_
Definition: JobQueue.h:68
void expectEarlyExit()
Once called, the Coro allows early exit without an assert.
bool runnable() const
Returns true if the Coro is still runnable (has not returned).
bool post()
Schedule coroutine execution.
std::mutex mutex_
Definition: JobQueue.h:66
detail::LocalValues lvs_
Definition: JobQueue.h:61
Coro & operator=(Coro const &)=delete
Coro(Coro_create_t, JobQueue &, JobType, std::string const &, F &&)
boost::coroutines::asymmetric_coroutine< void >::push_type * yield_
Definition: JobQueue.h:70
void yield() const
Suspend coroutine execution.
boost::coroutines::asymmetric_coroutine< void >::pull_type coro_
Definition: JobQueue.h:69
Coro(Coro const &)=delete
A pool of threads to perform work.
Definition: JobQueue.h:55
JobTypeData m_invalidJobData
Definition: JobQueue.h:252
int getJobLimit(JobType type)
Definition: JobQueue.cpp:440
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:410
std::atomic_bool stopped_
Definition: JobQueue.h:250
std::uint64_t m_lastJob
Definition: JobQueue.h:246
JobDataMap m_jobData
Definition: JobQueue.h:251
void rendezvous()
Block until no jobs running.
Definition: JobQueue.cpp:272
JobCounter jobCounter_
Definition: JobQueue.h:248
bool isStopping() const
Definition: JobQueue.h:229
int getJobCountTotal(JobType t) const
Jobs waiting plus running at this priority.
Definition: JobQueue.cpp:151
bool isStopped() const
Definition: JobQueue.cpp:321
bool isOverloaded()
Definition: JobQueue.cpp:205
perf::PerfLog & perfLog_
Definition: JobQueue.h:263
Workers m_workers
Definition: JobQueue.h:260
JobTypeData & getJobTypeData(JobType type)
Definition: JobQueue.cpp:279
int getJobCountGE(JobType t) const
All waiting jobs at or greater than this priority.
Definition: JobQueue.cpp:161
void addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
Add multiple load events.
Definition: JobQueue.cpp:192
Json::Value getJson(int c=0)
Definition: JobQueue.cpp:213
beast::insight::Collector::ptr m_collector
Definition: JobQueue.h:264
bool addRefCountedJob(JobType type, std::string const &name, JobFunction const &func)
Definition: JobQueue.cpp:81
std::unique_ptr< LoadEvent > makeLoadEvent(JobType t, std::string const &name)
Return a scoped LoadEvent.
Definition: JobQueue.cpp:178
std::set< Job > m_jobSet
Definition: JobQueue.h:247
int getJobCount(JobType t) const
Jobs waiting at this priority.
Definition: JobQueue.cpp:141
beast::insight::Hook hook
Definition: JobQueue.h:266
beast::Journal m_journal
Definition: JobQueue.h:244
std::mutex m_mutex
Definition: JobQueue.h:245
beast::insight::Gauge job_count
Definition: JobQueue.h:265
void processTask(int instance) override
Perform a task.
Definition: JobQueue.cpp:387
std::atomic_bool stopping_
Definition: JobQueue.h:249
void finishJob(JobType type)
Definition: JobQueue.cpp:364
void getNextJob(Job &job)
Definition: JobQueue.cpp:327
std::condition_variable cv_
Definition: JobQueue.h:268
bool addJob(JobType type, std::string const &name, JobHandler &&jobHandler)
Adds a job to the JobQueue.
Definition: JobQueue.h:165
Manages partitions for logging.
Definition: Log.h:51
Workers is effectively a thread pool.
Definition: Workers.h:81
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition: PerfLog.h:51
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
JobType
Definition: Job.h:34
Called to perform tasks as needed.
Definition: Workers.h:85