ThreadPool: optional limit for jobs queue (#1741)

For very busy servers, the internal jobs queue where accepted
sockets are enqueued can grow without limit.
This is a problem for two reasons:
 - queueing too much work causes the server to respond with huge latency,
   resulting in repetead timeouts on the clients; it is definitely
   better to reject the connection early, so that the client
   receives the backpressure signal as soon as the queue is
   becoming too large
 - the jobs list can eventually cause an out of memory condition
This commit is contained in:
vmaffione 2023-12-24 14:20:22 +01:00 committed by GitHub
parent 31cdcc3c3a
commit 374d058de7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 10 deletions

View file

@ -653,7 +653,7 @@ public:
TaskQueue() = default;
virtual ~TaskQueue() = default;
virtual void enqueue(std::function<void()> fn) = 0;
virtual bool enqueue(std::function<void()> fn) = 0;
virtual void shutdown() = 0;
virtual void on_idle() {}
@ -661,7 +661,8 @@ public:
class ThreadPool : public TaskQueue {
public:
explicit ThreadPool(size_t n) : shutdown_(false) {
explicit ThreadPool(size_t n, size_t mqr = 0)
: shutdown_(false), max_queued_requests_(mqr) {
while (n) {
threads_.emplace_back(worker(*this));
n--;
@ -671,13 +672,17 @@ public:
ThreadPool(const ThreadPool &) = delete;
~ThreadPool() override = default;
void enqueue(std::function<void()> fn) override {
bool enqueue(std::function<void()> fn) override {
{
std::unique_lock<std::mutex> lock(mutex_);
if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
return false;
}
jobs_.push_back(std::move(fn));
}
cond_.notify_one();
return true;
}
void shutdown() override {
@ -727,6 +732,7 @@ private:
std::list<std::function<void()>> jobs_;
bool shutdown_;
size_t max_queued_requests_ = 0;
std::condition_variable cond_;
std::mutex mutex_;
@ -6319,7 +6325,11 @@ inline bool Server::listen_internal() {
#endif
}
task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
if (!task_queue->enqueue(
[this, sock]() { process_and_close_socket(sock); })) {
detail::shutdown_socket(sock);
detail::close_socket(sock);
}
}
task_queue->shutdown();