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