mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 12:15:54 +00:00
149
src/rpc/WorkQueue.hpp
Normal file
149
src/rpc/WorkQueue.hpp
Normal file
@@ -0,0 +1,149 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2022, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/config/Config.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
#include "util/prometheus/Counter.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
/**
|
||||
* @brief An asynchronous, thread-safe queue for RPC requests.
|
||||
*/
|
||||
class WorkQueue {
|
||||
// these are cumulative for the lifetime of the process
|
||||
std::reference_wrapper<util::prometheus::CounterInt> queued_;
|
||||
std::reference_wrapper<util::prometheus::CounterInt> durationUs_;
|
||||
|
||||
std::reference_wrapper<util::prometheus::GaugeInt> curSize_;
|
||||
uint32_t maxSize_ = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
util::Logger log_{"RPC"};
|
||||
boost::asio::thread_pool ioc_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Create an we instance of the work queue.
|
||||
*
|
||||
* @param numWorkers The amount of threads to spawn in the pool
|
||||
* @param maxSize The maximum capacity of the queue; 0 means unlimited
|
||||
*/
|
||||
WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0);
|
||||
~WorkQueue();
|
||||
|
||||
/**
|
||||
* @brief A factory function that creates the work queue based on a config.
|
||||
*
|
||||
* @param config The Clio config to use
|
||||
*/
|
||||
static WorkQueue
|
||||
make_WorkQueue(util::Config const& config)
|
||||
{
|
||||
static util::Logger const log{"RPC"};
|
||||
auto const serverConfig = config.section("server");
|
||||
auto const numThreads = config.valueOr<uint32_t>("workers", std::thread::hardware_concurrency());
|
||||
auto const maxQueueSize = serverConfig.valueOr<uint32_t>("max_queue_size", 0); // 0 is no limit
|
||||
|
||||
LOG(log.info()) << "Number of workers = " << numThreads << ". Max queue size = " << maxQueueSize;
|
||||
return WorkQueue{numThreads, maxQueueSize};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Submit a job to the work queue.
|
||||
*
|
||||
* The job will be rejected if isWhiteListed is set to false and the current size of the queue reached capacity.
|
||||
*
|
||||
* @tparam FnType The function object type
|
||||
* @param func The function object to queue as a job
|
||||
* @param isWhiteListed Whether the queue capacity applies to this job
|
||||
* @return true if the job was successfully queued; false otherwise
|
||||
*/
|
||||
template <typename FnType>
|
||||
bool
|
||||
postCoro(FnType&& func, bool isWhiteListed)
|
||||
{
|
||||
if (curSize_.get().value() >= maxSize_ && !isWhiteListed) {
|
||||
LOG(log_.warn()) << "Queue is full. rejecting job. current size = " << curSize_.get().value()
|
||||
<< "; max size = " << maxSize_;
|
||||
return false;
|
||||
}
|
||||
|
||||
++curSize_.get();
|
||||
|
||||
// Each time we enqueue a job, we want to post a symmetrical job that will dequeue and run the job at the front
|
||||
// of the job queue.
|
||||
boost::asio::spawn(
|
||||
ioc_,
|
||||
[this, func = std::forward<FnType>(func), start = std::chrono::system_clock::now()](auto yield) mutable {
|
||||
auto const run = std::chrono::system_clock::now();
|
||||
auto const wait = std::chrono::duration_cast<std::chrono::microseconds>(run - start).count();
|
||||
|
||||
++queued_.get();
|
||||
durationUs_.get() += wait;
|
||||
LOG(log_.info()) << "WorkQueue wait time = " << wait << " queue size = " << curSize_.get().value();
|
||||
|
||||
func(yield);
|
||||
--curSize_.get();
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a report of the work queue state.
|
||||
*
|
||||
* @return The report as a JSON object.
|
||||
*/
|
||||
boost::json::object
|
||||
report() const
|
||||
{
|
||||
auto obj = boost::json::object{};
|
||||
|
||||
obj["queued"] = queued_.get().value();
|
||||
obj["queued_duration_us"] = durationUs_.get().value();
|
||||
obj["current_queue_size"] = curSize_.get().value();
|
||||
obj["max_queue_size"] = maxSize_;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait until all the jobs in the queue are finished.
|
||||
*/
|
||||
void
|
||||
join();
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
Reference in New Issue
Block a user