rippled
JobQueue.cpp
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 #include <ripple/core/JobQueue.h>
21 #include <ripple/basics/contract.h>
22 #include <ripple/basics/PerfLog.h>
23 
24 namespace ripple {
25 
27  Stoppable& parent, beast::Journal journal, Logs& logs,
28  perf::PerfLog& perfLog)
29  : Stoppable ("JobQueue", parent)
30  , m_journal (journal)
31  , m_lastJob (0)
32  , m_invalidJobData (JobTypes::instance().getInvalid (), collector, logs)
33  , m_processCount (0)
34  , m_workers (*this, &perfLog, "JobQueue", 0)
35  , m_cancelCallback (std::bind (&Stoppable::isStopping, this))
36  , perfLog_ (perfLog)
37  , m_collector (collector)
38 {
39  hook = m_collector->make_hook (std::bind (&JobQueue::collect, this));
40  job_count = m_collector->make_gauge ("job_count");
41 
42  {
43  std::lock_guard lock (m_mutex);
44 
45  for (auto const& x : JobTypes::instance())
46  {
47  JobTypeInfo const& jt = x.second;
48 
49  // And create dynamic information for all jobs
50  auto const result (m_jobData.emplace (std::piecewise_construct,
52  std::forward_as_tuple (jt, m_collector, logs)));
53  assert (result.second == true);
54  (void) result.second;
55  }
56  }
57 }
58 
60 {
61  // Must unhook before destroying
63 }
64 
65 void
67 {
68  std::lock_guard lock (m_mutex);
69  job_count = m_jobSet.size ();
70 }
71 
72 bool
74  JobFunction const& func)
75 {
76  assert (type != jtINVALID);
77 
78  auto iter (m_jobData.find (type));
79  assert (iter != m_jobData.end ());
80  if (iter == m_jobData.end ())
81  return false;
82 
83  JobTypeData& data (iter->second);
84 
85  // FIXME: Workaround incorrect client shutdown ordering
86  // do not add jobs to a queue with no threads
87  assert (type == jtCLIENT || m_workers.getNumberOfThreads () > 0);
88 
89  {
90  std::lock_guard lock (m_mutex);
91 
92  // If this goes off it means that a child didn't follow
93  // the Stoppable API rules. A job may only be added if:
94  //
95  // - The JobQueue has NOT stopped
96  // AND
97  // * We are currently processing jobs
98  // OR
99  // * We have have pending jobs
100  // OR
101  // * Not all children are stopped
102  //
103  assert (! isStopped() && (
104  m_processCount>0 ||
105  ! m_jobSet.empty () ||
106  ! areChildrenStopped()));
107 
109  m_jobSet.insert (Job (type, name, ++m_lastJob,
110  data.load (), func, m_cancelCallback)));
111  queueJob (*result.first, lock);
112  }
113  return true;
114 }
115 
116 int
118 {
119  std::lock_guard lock (m_mutex);
120 
121  JobDataMap::const_iterator c = m_jobData.find (t);
122 
123  return (c == m_jobData.end ())
124  ? 0
125  : c->second.waiting;
126 }
127 
128 int
130 {
131  std::lock_guard lock (m_mutex);
132 
133  JobDataMap::const_iterator c = m_jobData.find (t);
134 
135  return (c == m_jobData.end ())
136  ? 0
137  : (c->second.waiting + c->second.running);
138 }
139 
140 int
142 {
143  // return the number of jobs at this priority level or greater
144  int ret = 0;
145 
146  std::lock_guard lock (m_mutex);
147 
148  for (auto const& x : m_jobData)
149  {
150  if (x.first >= t)
151  ret += x.second.waiting;
152  }
153 
154  return ret;
155 }
156 
157 void
158 JobQueue::setThreadCount (int c, bool const standaloneMode)
159 {
160  if (standaloneMode)
161  {
162  c = 1;
163  }
164  else if (c == 0)
165  {
166  c = static_cast<int>(std::thread::hardware_concurrency());
167  c = 2 + std::min(c, 4); // I/O will bottleneck
168  JLOG (m_journal.info()) << "Auto-tuning to " << c <<
169  " validation/transaction/proposal threads.";
170  }
171  else
172  {
173  JLOG (m_journal.info()) << "Configured " << c <<
174  " validation/transaction/proposal threads.";
175  }
176 
178 }
179 
182 {
183  JobDataMap::iterator iter (m_jobData.find (t));
184  assert (iter != m_jobData.end ());
185 
186  if (iter == m_jobData.end ())
187  return {};
188 
189  return std::make_unique<LoadEvent> (iter-> second.load (), name, true);
190 }
191 
192 void
195 {
196  if (isStopped())
197  LogicError ("JobQueue::addLoadEvents() called after JobQueue stopped");
198 
199  JobDataMap::iterator iter (m_jobData.find (t));
200  assert (iter != m_jobData.end ());
201  iter->second.load().addSamples (count, elapsed);
202 }
203 
204 bool
206 {
207  int count = 0;
208 
209  for (auto& x : m_jobData)
210  {
211  if (x.second.load ().isOver ())
212  ++count;
213  }
214 
215  return count > 0;
216 }
217 
220 {
221  using namespace std::chrono_literals;
223 
224  ret["threads"] = m_workers.getNumberOfThreads ();
225 
226  Json::Value priorities = Json::arrayValue;
227 
228  std::lock_guard lock (m_mutex);
229 
230  for (auto& x : m_jobData)
231  {
232  assert (x.first != jtINVALID);
233 
234  if (x.first == jtGENERIC)
235  continue;
236 
237  JobTypeData& data (x.second);
238 
239  LoadMonitor::Stats stats (data.stats ());
240 
241  int waiting (data.waiting);
242  int running (data.running);
243 
244  if ((stats.count != 0) || (waiting != 0) ||
245  (stats.latencyPeak != 0ms) || (running != 0))
246  {
247  Json::Value& pri = priorities.append (Json::objectValue);
248 
249  pri["job_type"] = data.name ();
250 
251  if (stats.isOverloaded)
252  pri["over_target"] = true;
253 
254  if (waiting != 0)
255  pri["waiting"] = waiting;
256 
257  if (stats.count != 0)
258  pri["per_second"] = static_cast<int> (stats.count);
259 
260  if (stats.latencyPeak != 0ms)
261  pri["peak_time"] = static_cast<int> (stats.latencyPeak.count());
262 
263  if (stats.latencyAvg != 0ms)
264  pri["avg_time"] = static_cast<int> (stats.latencyAvg.count());
265 
266  if (running != 0)
267  pri["in_progress"] = running;
268  }
269  }
270 
271  ret["job_types"] = priorities;
272 
273  return ret;
274 }
275 
276 void
278 {
280  cv_.wait(lock, [&]
281  {
282  return m_processCount == 0 &&
283  m_jobSet.empty();
284  });
285 }
286 
289 {
290  JobDataMap::iterator c (m_jobData.find (type));
291  assert (c != m_jobData.end ());
292 
293  // NIKB: This is ugly and I hate it. We must remove jtINVALID completely
294  // and use something sane.
295  if (c == m_jobData.end ())
296  return m_invalidJobData;
297 
298  return c->second;
299 }
300 
301 void
303 {
304  // onStop must be defined and empty here,
305  // otherwise the base class will do the wrong thing.
306 }
307 
308 void
310 {
311  // We are stopped when all of the following are true:
312  //
313  // 1. A stop notification was received
314  // 2. All Stoppable children have stopped
315  // 3. There are no executing calls to processTask
316  // 4. There are no remaining Jobs in the job set
317  // 5. There are no suspended coroutines
318  //
319  if (isStopping() &&
320  areChildrenStopped() &&
321  (m_processCount == 0) &&
322  m_jobSet.empty() &&
323  nSuspend_ == 0)
324  {
325  stopped();
326  }
327 }
328 
329 void
331 {
332  JobType const type (job.getType ());
333  assert (type != jtINVALID);
334  assert (m_jobSet.find (job) != m_jobSet.end ());
335  perfLog_.jobQueue(type);
336 
337  JobTypeData& data (getJobTypeData (type));
338 
339  if (data.waiting + data.running < getJobLimit (type))
340  {
341  m_workers.addTask ();
342  }
343  else
344  {
345  // defer the task until we go below the limit
346  //
347  ++data.deferred;
348  }
349  ++data.waiting;
350 }
351 
352 void
354 {
355  assert (! m_jobSet.empty ());
356 
358  for (iter = m_jobSet.begin (); iter != m_jobSet.end (); ++iter)
359  {
360  JobTypeData& data (getJobTypeData (iter->getType ()));
361 
362  assert (data.running <= getJobLimit (data.type ()));
363 
364  // Run this job if we're running below the limit.
365  if (data.running < getJobLimit (data.type ()))
366  {
367  assert (data.waiting > 0);
368  break;
369  }
370  }
371 
372  assert (iter != m_jobSet.end ());
373 
374  JobType const type = iter->getType ();
375  JobTypeData& data (getJobTypeData (type));
376 
377  assert (type != jtINVALID);
378 
379  job = *iter;
380  m_jobSet.erase (iter);
381 
382  --data.waiting;
383  ++data.running;
384 }
385 
386 void
388 {
389  assert(type != jtINVALID);
390 
391  JobTypeData& data = getJobTypeData (type);
392 
393  // Queue a deferred task if possible
394  if (data.deferred > 0)
395  {
396  assert (data.running + data.waiting >= getJobLimit (type));
397 
398  --data.deferred;
399  m_workers.addTask ();
400  }
401 
402  --data.running;
403 }
404 
405 void
406 JobQueue::processTask (int instance)
407 {
408  JobType type;
409 
410  {
411  using namespace std::chrono;
412  Job::clock_type::time_point const start_time (
414  {
415  Job job;
416  {
417  std::lock_guard lock (m_mutex);
418  getNextJob (job);
419  ++m_processCount;
420  }
421  type = job.getType();
422  JobTypeData& data(getJobTypeData(type));
423  JLOG(m_journal.trace()) << "Doing " << data.name () << "job";
424 
425  //The amount of time that the job was in the queue
426  auto const q_time = date::ceil<microseconds>(
427  start_time - job.queue_time());
428  perfLog_.jobStart(type, q_time, start_time, instance);
429 
430  job.doJob ();
431 
432  //The amount of time it took to execute the job
433  auto const x_time = date::ceil<microseconds>(
434  Job::clock_type::now() - start_time);
435 
436  if (x_time >= 10ms || q_time >= 10ms)
437  {
438  getJobTypeData(type).dequeue.notify(q_time);
439  getJobTypeData(type).execute.notify(x_time);
440  }
441  perfLog_.jobFinish(type, x_time, instance);
442  }
443 
444  }
445 
446  {
447  std::lock_guard lock (m_mutex);
448  // Job should be destroyed before calling checkStopped
449  // otherwise destructors with side effects can access
450  // parent objects that are already destroyed.
451  finishJob (type);
452  if(--m_processCount == 0 && m_jobSet.empty())
453  cv_.notify_all();
454  checkStopped (lock);
455  }
456 
457  // Note that when Job::~Job is called, the last reference
458  // to the associated LoadEvent object (in the Job) may be destroyed.
459 }
460 
461 int
463 {
464  JobTypeInfo const& j (JobTypes::instance().get (type));
465  assert (j.type () != jtINVALID);
466 
467  return j.limit ();
468 }
469 
470 void
472 {
473  std::lock_guard lock (m_mutex);
474  checkStopped (lock);
475 }
476 
477 }
ripple::JobQueue::finishJob
void finishJob(JobType type)
Definition: JobQueue.cpp:387
ripple::JobQueue::m_jobSet
std::set< Job > m_jobSet
Definition: JobQueue.h:222
ripple::JobQueue::nSuspend_
int nSuspend_
Definition: JobQueue.h:230
std::bind
T bind(T... args)
std::string
STL class.
std::shared_ptr< Collector >
ripple::jtCLIENT
@ jtCLIENT
Definition: Job.h:49
ripple::Logs
Manages partitions for logging.
Definition: Log.h:49
ripple::Stoppable::stopped
void stopped()
Called by derived classes to indicate that the stoppable has stopped.
Definition: Stoppable.cpp:71
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:287
ripple::JobTypes
Definition: JobTypes.h:33
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:44
std::pair
ripple::JobTypeData::execute
beast::insight::Event execute
Definition: JobTypeData.h:53
std::map::find
T find(T... args)
ripple::Workers::getNumberOfThreads
int getNumberOfThreads() const noexcept
Retrieve the desired number of threads.
Definition: Workers.cpp:52
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::getJson
Json::Value getJson(int c=0)
Definition: JobQueue.cpp:219
ripple::LoadMonitor::Stats::count
std::uint64_t count
Definition: LoadMonitor.h:56
ripple::LoadMonitor::Stats::isOverloaded
bool isOverloaded
Definition: LoadMonitor.h:59
ripple::JobQueue::checkStopped
void checkStopped(std::lock_guard< std::mutex > const &lock)
Definition: JobQueue.cpp:309
std::map::emplace
T emplace(T... args)
ripple::Workers::setNumberOfThreads
void setNumberOfThreads(int numberOfThreads)
Set the desired number of threads.
Definition: Workers.cpp:61
ripple::Job::queue_time
clock_type::time_point const & queue_time() const
Returns the time when the job was queued.
Definition: Job.cpp:65
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
std::function
ripple::JobQueue::m_journal
beast::Journal m_journal
Definition: JobQueue.h:219
ripple::JobTypeInfo::limit
int limit() const
Definition: JobTypeInfo.h:70
ripple::JobQueue::onStop
void onStop() override
Override called when the stop notification is issued.
Definition: JobQueue.cpp:302
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::JobTypeData::dequeue
beast::insight::Event dequeue
Definition: JobTypeData.h:52
ripple::JobQueue::getJobCountTotal
int getJobCountTotal(JobType t) const
Jobs waiting plus running at this priority.
Definition: JobQueue.cpp:129
ripple::Stoppable::isStopped
bool isStopped() const
Returns true if the requested stop has completed.
Definition: Stoppable.cpp:61
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:907
ripple::LoadMonitor::Stats
Definition: LoadMonitor.h:52
ripple::Stoppable
Provides an interface for starting and stopping.
Definition: Stoppable.h:200
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:45
ripple::JobQueue::~JobQueue
~JobQueue()
Definition: JobQueue.cpp:59
ripple::JobQueue::hook
beast::insight::Hook hook
Definition: JobQueue.h:239
std::thread::hardware_concurrency
T hardware_concurrency(T... args)
ripple::JobQueue::m_mutex
std::mutex m_mutex
Definition: JobQueue.h:220
ripple::JobTypeInfo::type
JobType type() const
Definition: JobTypeInfo.h:60
ripple::JobQueue::m_collector
beast::insight::Collector::ptr m_collector
Definition: JobQueue.h:237
ripple::JobQueue::isOverloaded
bool isOverloaded()
Definition: JobQueue.cpp:205
std::unique_lock
STL class.
ripple::JobTypeInfo
Holds all the 'static' information about a job, which does not change.
Definition: JobTypeInfo.h:27
ripple::jtGENERIC
@ jtGENERIC
Definition: Job.h:75
ripple::Stoppable::areChildrenStopped
bool areChildrenStopped() const
Returns true if all children have stopped.
Definition: Stoppable.cpp:66
ripple::jtINVALID
@ jtINVALID
Definition: Job.h:36
ripple::JobTypes::instance
static JobTypes const & instance()
Definition: JobTypes.h:87
ripple::JobQueue::processTask
void processTask(int instance) override
Perform a task.
Definition: JobQueue.cpp:406
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
beast::Journal::info
Stream info() const
Definition: Journal.h:297
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::condition_variable::wait
T wait(T... args)
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::perf::PerfLog::jobQueue
virtual void jobQueue(JobType const type)=0
Log queued job.
ripple::perf::PerfLog::jobStart
virtual void jobStart(JobType const type, microseconds dur, steady_time_point startTime, int instance)=0
Log job executing.
std::forward_as_tuple
T forward_as_tuple(T... args)
ripple::JobQueue::rendezvous
void rendezvous()
Block until no tasks running.
Definition: JobQueue.cpp:277
ripple::JobQueue::m_cancelCallback
Job::CancelCallback m_cancelCallback
Definition: JobQueue.h:233
std::min
T min(T... args)
ripple::JobQueue::getJobCountGE
int getJobCountGE(JobType t) const
All waiting jobs at or greater than this priority.
Definition: JobQueue.cpp:141
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
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
std
STL namespace.
ripple::LogicError
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:50
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
std::chrono::milliseconds::count
T count(T... args)
ripple::Workers::addTask
void addTask()
Add a task to be performed.
Definition: Workers.cpp:122
ripple::LoadMonitor::Stats::latencyAvg
std::chrono::milliseconds latencyAvg
Definition: LoadMonitor.h:57
ripple::JobType
JobType
Definition: Job.h:33
ripple::JobQueue::job_count
beast::insight::Gauge job_count
Definition: JobQueue.h:238
std::map::end
T end(T... args)
ripple::Job::getType
JobType getType() const
Definition: Job.cpp:54
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::Job::doJob
void doJob()
Definition: Job.cpp:77
std::unique_ptr
STL class.
ripple::LoadMonitor::Stats::latencyPeak
std::chrono::milliseconds latencyPeak
Definition: LoadMonitor.h:58
beast::insight::Hook
A reference to a handler for performing polled collection.
Definition: Hook.h:31
std::condition_variable::notify_all
T notify_all(T... args)
std::set
STL class.
ripple::JobQueue::getNextJob
void getNextJob(Job &job)
Definition: JobQueue.cpp:353
ripple::perf::PerfLog::jobFinish
virtual void jobFinish(JobType const type, microseconds dur, int instance)=0
Log job finishing.
Json::Value
Represents a JSON value.
Definition: json_value.h:141
beast::insight::Event::notify
void notify(std::chrono::duration< Rep, Period > const &value) const
Push an event notification.
Definition: Event.h:65
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:124
ripple::JobQueue::collect
void collect()
Definition: JobQueue.cpp:66
ripple::Stoppable::isStopping
bool isStopping() const
Returns true if the stoppable should stop.
Definition: Stoppable.cpp:56
std::chrono
std::chrono::steady_clock::now
T now(T... args)