rippled
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 <ripple/basics/LocalValue.h>
24 #include <ripple/core/ClosureCounter.h>
25 #include <ripple/core/JobTypeData.h>
26 #include <ripple/core/JobTypes.h>
27 #include <ripple/core/impl/Workers.h>
28 #include <ripple/json/json_value.h>
29 #include <boost/coroutine/all.hpp>
30 #include <boost/range/begin.hpp> // workaround for boost 1.72 bug
31 #include <boost/range/end.hpp> // workaround for boost 1.72 bug
32 
33 namespace ripple {
34 
35 namespace perf {
36 class PerfLog;
37 }
38 
39 class Logs;
41 {
42  explicit Coro_create_t() = default;
43 };
44 
55 class JobQueue : private Workers::Callback
56 {
57 public:
59  class Coro : public std::enable_shared_from_this<Coro>
60  {
61  private:
66  bool running_;
70  boost::coroutines::asymmetric_coroutine<void>::pull_type coro_;
71  boost::coroutines::asymmetric_coroutine<void>::push_type* yield_;
72 #ifndef NDEBUG
73  bool finished_ = false;
74 #endif
75 
76  public:
77  // Private: Used in the implementation
78  template <class F>
80 
81  // Not copy-constructible or assignable
82  Coro(Coro const&) = delete;
83  Coro&
84  operator=(Coro const&) = delete;
85 
86  ~Coro();
87 
97  void
98  yield() const;
99 
113  bool
114  post();
115 
125  void
126  resume();
127 
129  bool
130  runnable() const;
131 
133  void
134  expectEarlyExit();
135 
137  void
138  join();
139  };
140 
141  using JobFunction = std::function<void(Job&)>;
142 
143  JobQueue(
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,
161  typename = std::enable_if_t<std::is_same<
162  decltype(std::declval<JobHandler&&>()(std::declval<Job&>())),
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 
205  void
206  setThreadCount(int c, bool const standaloneMode);
207 
211  makeLoadEvent(JobType t, std::string const& name);
212 
215  void
216  addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed);
217 
218  // Cannot be const because LoadMonitor has no const methods.
219  bool
220  isOverloaded();
221 
222  // Cannot be const because LoadMonitor has no const methods.
224  getJson(int c = 0);
225 
227  void
228  rendezvous();
229 
230  void
231  stop();
232 
233  bool
234  isStopping() const
235  {
236  return stopping_;
237  }
238 
239  // We may be able to move away from this, but we can keep it during the
240  // transition.
241  bool
242  isStopped() const;
243 
244 private:
245  friend class Coro;
246 
248 
258 
259  // The number of jobs currently in processTask()
261 
262  // The number of suspended coroutines
263  int nSuspend_ = 0;
264 
267 
268  // Statistics tracking
273 
275 
276  void
277  collect();
278  JobTypeData&
279  getJobTypeData(JobType type);
280 
281  // Adds a reference counted job to the JobQueue.
282  //
283  // param type The type of job.
284  // param name Name of the job.
285  // param func std::function with signature void (Job&). Called when the
286  // job is executed.
287  //
288  // return true if func added to queue.
289  bool
291  JobType type,
292  std::string const& name,
293  JobFunction const& func);
294 
295  // Signals an added Job for processing.
296  //
297  // Pre-conditions:
298  // The JobType must be valid.
299  // The Job must exist in mJobSet.
300  // The Job must not have previously been queued.
301  //
302  // Post-conditions:
303  // Count of waiting jobs of that type will be incremented.
304  // If JobQueue exists, and has at least one thread, Job will eventually
305  // run.
306  //
307  // Invariants:
308  // The calling thread owns the JobLock
309  void
310  queueJob(Job const& job, std::lock_guard<std::mutex> const& lock);
311 
312  // Returns the next Job we should run now.
313  //
314  // RunnableJob:
315  // A Job in the JobSet whose slots count for its type is greater than zero.
316  //
317  // Pre-conditions:
318  // mJobSet must not be empty.
319  // mJobSet holds at least one RunnableJob
320  //
321  // Post-conditions:
322  // job is a valid Job object.
323  // job is removed from mJobQueue.
324  // Waiting job count of its type is decremented
325  // Running job count of its type is incremented
326  //
327  // Invariants:
328  // The calling thread owns the JobLock
329  void
330  getNextJob(Job& job);
331 
332  // Indicates that a running Job has completed its task.
333  //
334  // Pre-conditions:
335  // Job must not exist in mJobSet.
336  // The JobType must not be invalid.
337  //
338  // Post-conditions:
339  // The running count of that JobType is decremented
340  // A new task is signaled if there are more waiting Jobs than the limit, if
341  // any.
342  //
343  // Invariants:
344  // <none>
345  void
346  finishJob(JobType type);
347 
348  // Runs the next appropriate waiting Job.
349  //
350  // Pre-conditions:
351  // A RunnableJob must exist in the JobSet
352  //
353  // Post-conditions:
354  // The chosen RunnableJob will have Job::doJob() called.
355  //
356  // Invariants:
357  // <none>
358  void
359  processTask(int instance) override;
360 
361  // Returns the limit of running jobs for the given job type.
362  // For jobs with no limit, we return the largest int. Hopefully that
363  // will be enough.
364  int
365  getJobLimit(JobType type);
366 };
367 
368 /*
369  An RPC command is received and is handled via ServerHandler(HTTP) or
370  Handler(websocket), depending on the connection type. The handler then calls
371  the JobQueue::postCoro() method to create a coroutine and run it at a later
372  point. This frees up the handler thread and allows it to continue handling
373  other requests while the RPC command completes its work asynchronously.
374 
375  postCoro() creates a Coro object. When the Coro ctor is called, and its
376  coro_ member is initialized (a boost::coroutines::pull_type), execution
377  automatically passes to the coroutine, which we don't want at this point,
378  since we are still in the handler thread context. It's important to note
379  here that construction of a boost pull_type automatically passes execution to
380  the coroutine. A pull_type object automatically generates a push_type that is
381  passed as a parameter (do_yield) in the signature of the function the
382  pull_type was created with. This function is immediately called during coro_
383  construction and within it, Coro::yield_ is assigned the push_type
384  parameter (do_yield) address and called (yield()) so we can return execution
385  back to the caller's stack.
386 
387  postCoro() then calls Coro::post(), which schedules a job on the job
388  queue to continue execution of the coroutine in a JobQueue worker thread at
389  some later time. When the job runs, we lock on the Coro::mutex_ and call
390  coro_ which continues where we had left off. Since we the last thing we did
391  in coro_ was call yield(), the next thing we continue with is calling the
392  function param f, that was passed into Coro ctor. It is within this
393  function body that the caller specifies what he would like to do while
394  running in the coroutine and allow them to suspend and resume execution.
395  A task that relies on other events to complete, such as path finding, calls
396  Coro::yield() to suspend its execution while waiting on those events to
397  complete and continue when signaled via the Coro::post() method.
398 
399  There is a potential race condition that exists here where post() can get
400  called before yield() after f is called. Technically the problem only occurs
401  if the job that post() scheduled is executed before yield() is called.
402  If the post() job were to be executed before yield(), undefined behavior
403  would occur. The lock ensures that coro_ is not called again until we exit
404  the coroutine. At which point a scheduled resume() job waiting on the lock
405  would gain entry, harmlessly call coro_ and immediately return as we have
406  already completed the coroutine.
407 
408  The race condition occurs as follows:
409 
410  1- The coroutine is running.
411  2- The coroutine is about to suspend, but before it can do so, it must
412  arrange for some event to wake it up.
413  3- The coroutine arranges for some event to wake it up.
414  4- Before the coroutine can suspend, that event occurs and the
415  resumption of the coroutine is scheduled on the job queue. 5- Again, before
416  the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
417  Again, before the coroutine can suspend, the resumption code runs the
418  coroutine.
419  The coroutine is now running in two threads.
420 
421  The lock prevents this from happening as step 6 will block until the
422  lock is released which only happens after the coroutine completes.
423 */
424 
425 } // namespace ripple
426 
427 #include <ripple/core/Coro.ipp>
428 
429 namespace ripple {
430 
431 template <class F>
433 JobQueue::postCoro(JobType t, std::string const& name, F&& f)
434 {
435  /* First param is a detail type to make construction private.
436  Last param is the function the coroutine runs. Signature of
437  void(std::shared_ptr<Coro>).
438  */
439  auto coro = std::make_shared<Coro>(
440  Coro_create_t{}, *this, t, name, std::forward<F>(f));
441  if (!coro->post())
442  {
443  // The Coro was not successfully posted. Disable it so it's destructor
444  // can run with no negative side effects. Then destroy it.
445  coro->expectEarlyExit();
446  coro.reset();
447  }
448  return coro;
449 }
450 
451 } // namespace ripple
452 
453 #endif
ripple::Coro_create_t::Coro_create_t
Coro_create_t()=default
ripple::JobQueue::finishJob
void finishJob(JobType type)
Definition: JobQueue.cpp:367
std::is_same
ripple::JobQueue::m_jobSet
std::set< Job > m_jobSet
Definition: JobQueue.h:252
ripple::JobQueue::nSuspend_
int nSuspend_
Definition: JobQueue.h:263
std::string
STL class.
std::shared_ptr< Collector >
ripple::JobQueue::postCoro
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:433
ripple::Logs
Manages partitions for logging.
Definition: Log.h:48
ripple::JobQueue::Coro::post
bool post()
Schedule coroutine execution.
ripple::JobQueue::getJobLimit
int getJobLimit(JobType type)
Definition: JobQueue.cpp:439
std::chrono::milliseconds
ripple::JobQueue::Coro::coro_
boost::coroutines::asymmetric_coroutine< void >::pull_type coro_
Definition: JobQueue.h:70
ripple::JobQueue::Coro::expectEarlyExit
void expectEarlyExit()
Once called, the Coro allows early exit without an assert.
ripple::JobQueue::getJson
Json::Value getJson(int c=0)
Definition: JobQueue.cpp:203
std::lock_guard
STL class.
ripple::perf::PerfLog
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition: PerfLog.h:47
ripple::JobQueue::addRefCountedJob
bool addRefCountedJob(JobType type, std::string const &name, JobFunction const &func)
Definition: JobQueue.cpp:76
ripple::JobQueue::addJob
bool addJob(JobType type, std::string const &name, JobHandler &&jobHandler)
Adds a job to the JobQueue.
Definition: JobQueue.h:165
std::function
ripple::JobQueue::Coro::jq_
JobQueue & jq_
Definition: JobQueue.h:63
ripple::Workers::Callback
Called to perform tasks as needed.
Definition: Workers.h:72
ripple::JobQueue::m_journal
beast::Journal m_journal
Definition: JobQueue.h:249
ripple::JobQueue::Coro
Coroutines must run to completion.
Definition: JobQueue.h:59
ripple::JobQueue::JobQueue
JobQueue(beast::insight::Collector::ptr const &collector, beast::Journal journal, Logs &logs, perf::PerfLog &perfLog)
Definition: JobQueue.cpp:27
ripple::JobQueue::Coro::~Coro
~Coro()
ripple::JobQueue::getJobCount
int getJobCount(JobType t) const
Jobs waiting at this priority.
Definition: JobQueue.cpp:106
ripple::JobQueue::jobCounter_
JobCounter jobCounter_
Definition: JobQueue.h:253
ripple::JobQueue::m_invalidJobData
JobTypeData m_invalidJobData
Definition: JobQueue.h:257
ripple::JobQueue::getJobCountTotal
int getJobCountTotal(JobType t) const
Jobs waiting plus running at this priority.
Definition: JobQueue.cpp:116
ripple::JobQueue::Coro::resume
void resume()
Resume coroutine execution.
ripple::JobQueue::Coro::lvs_
detail::LocalValues lvs_
Definition: JobQueue.h:62
ripple::JobQueue::Coro::Coro
Coro(Coro_create_t, JobQueue &, JobType, std::string const &, F &&)
ripple::JobQueue::~JobQueue
~JobQueue()
Definition: JobQueue.cpp:62
ripple::JobQueue::hook
beast::insight::Hook hook
Definition: JobQueue.h:272
std::enable_if_t
ripple::JobQueue::Coro::cv_
std::condition_variable cv_
Definition: JobQueue.h:69
ripple::JobQueue::m_mutex
std::mutex m_mutex
Definition: JobQueue.h:250
ripple::JobQueue::m_collector
beast::insight::Collector::ptr m_collector
Definition: JobQueue.h:270
ripple::JobQueue::isOverloaded
bool isOverloaded()
Definition: JobQueue.cpp:189
ripple::JobQueue::isStopping
bool isStopping() const
Definition: JobQueue.h:234
ripple::JobQueue::processTask
void processTask(int instance) override
Perform a task.
Definition: JobQueue.cpp:386
std::enable_shared_from_this
ripple::JobQueue::m_jobData
JobDataMap m_jobData
Definition: JobQueue.h:256
ripple::JobTypeData
Definition: JobTypeData.h:29
ripple::Job
Definition: Job.h:85
ripple::ClosureCounter< void, Job & >
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint64_t
ripple::JobQueue::getJobTypeData
JobTypeData & getJobTypeData(JobType type)
Definition: JobQueue.cpp:268
ripple::JobQueue::m_lastJob
std::uint64_t m_lastJob
Definition: JobQueue.h:251
ripple::JobQueue::isStopped
bool isStopped() const
Definition: JobQueue.cpp:304
std::atomic_bool
ripple::JobQueue::Coro::yield
void yield() const
Suspend coroutine execution.
std::map< JobType, JobTypeData >
ripple::JobQueue::stopping_
std::atomic_bool stopping_
Definition: JobQueue.h:254
ripple::JobQueue::stopped_
std::atomic_bool stopped_
Definition: JobQueue.h:255
ripple::JobQueue::rendezvous
void rendezvous()
Block until no jobs running.
Definition: JobQueue.cpp:261
ripple::JobQueue::Coro::join
void join()
Waits until coroutine returns from the user function.
ripple::JobQueue::Coro::type_
JobType type_
Definition: JobQueue.h:64
ripple::Workers
Workers is effectively a thread pool.
Definition: Workers.h:68
beast::insight::Gauge
A metric for measuring an integral value.
Definition: Gauge.h:39
ripple::JobQueue::m_cancelCallback
Job::CancelCallback m_cancelCallback
Definition: JobQueue.h:266
ripple::JobQueue
A pool of threads to perform work.
Definition: JobQueue.h:55
ripple::JobQueue::stop
void stop()
Definition: JobQueue.cpp:282
ripple::JobQueue::getJobCountGE
int getJobCountGE(JobType t) const
All waiting jobs at or greater than this priority.
Definition: JobQueue.cpp:126
ripple::JobQueue::Coro::name_
std::string name_
Definition: JobQueue.h:65
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::JobQueue::Coro::yield_
boost::coroutines::asymmetric_coroutine< void >::push_type * yield_
Definition: JobQueue.h:71
ripple::JobQueue::queueJob
void queueJob(Job const &job, std::lock_guard< std::mutex > const &lock)
Definition: JobQueue.cpp:310
ripple::JobQueue::perfLog_
perf::PerfLog & perfLog_
Definition: JobQueue.h:269
ripple::JobQueue::setThreadCount
void setThreadCount(int c, bool const standaloneMode)
Set the number of thread serving the job queue to precisely this number.
Definition: JobQueue.cpp:143
ripple::JobQueue::Coro::finished_
bool finished_
Definition: JobQueue.h:73
std::condition_variable
ripple::Coro_create_t
Definition: JobQueue.h:40
std::mutex
STL class.
ripple::JobType
JobType
Definition: Job.h:34
ripple::JobQueue::job_count
beast::insight::Gauge job_count
Definition: JobQueue.h:271
ripple::JobQueue::Coro::mutex_run_
std::mutex mutex_run_
Definition: JobQueue.h:68
ripple::JobQueue::Coro::mutex_
std::mutex mutex_
Definition: JobQueue.h:67
ripple::JobQueue::m_processCount
int m_processCount
Definition: JobQueue.h:260
ripple::JobQueue::cv_
std::condition_variable cv_
Definition: JobQueue.h:274
ripple::JobQueue::makeLoadEvent
std::unique_ptr< LoadEvent > makeLoadEvent(JobType t, std::string const &name)
Return a scoped LoadEvent.
Definition: JobQueue.cpp:166
ripple::JobQueue::m_workers
Workers m_workers
Definition: JobQueue.h:265
ripple::JobQueue::addLoadEvents
void addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
Add multiple load events.
Definition: JobQueue.cpp:178
ripple::detail::LocalValues
Definition: LocalValue.h:31
std::unique_ptr
STL class.
beast::insight::Hook
A reference to a handler for performing polled collection.
Definition: Hook.h:31
std::set
STL class.
ripple::JobQueue::getNextJob
void getNextJob(Job &job)
Definition: JobQueue.cpp:333
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::JobQueue::Coro::operator=
Coro & operator=(Coro const &)=delete
ripple::JobQueue::Coro::running_
bool running_
Definition: JobQueue.h:66
ripple::JobQueue::collect
void collect()
Definition: JobQueue.cpp:69
ripple::JobQueue::Coro::runnable
bool runnable() const
Returns true if the Coro is still runnable (has not returned).
ripple::ClosureCounter::wrap
std::optional< Substitute< Closure > > wrap(Closure &&closure)
Wrap the passed closure with a reference counter.
Definition: ClosureCounter.h:192