1
0
mirror of synced 2026-10-24 00:28:57 +03:00

Make ThreadPool idle timeout configurable at runtime (Fix #2481)

This commit is contained in:
yhirose
2026-07-01 22:08:01 -04:00
parent 45da614ddd
commit 32ff75e355
3 changed files with 58 additions and 6 deletions

View File

@@ -824,6 +824,15 @@ svr.new_task_queue = [] { return new ThreadPool(/*base_threads=*/12, /*max_threa
Default limit is 0 (unlimited). Once the limit is reached, the listener
will shutdown the client connection.
#### Idle timeout for dynamic threads
The idle timeout for dynamic threads can also be set at runtime via the
fourth parameter (in seconds):
```cpp
svr.new_task_queue = [] { return new ThreadPool(/*base_threads=*/8, /*max_threads=*/64, /*max_queued_requests=*/0, /*idle_timeout_sec=*/10); };
```
### Override the default thread pool with yours
You can supply your own thread pool implementation according to your need.

View File

@@ -1541,7 +1541,9 @@ public:
class ThreadPool final : public TaskQueue {
public:
explicit ThreadPool(size_t n, size_t max_n = 0, size_t mqr = 0);
explicit ThreadPool(
size_t n, size_t max_n = 0, size_t mqr = 0,
time_t idle_timeout_sec = CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT);
ThreadPool(const ThreadPool &) = delete;
~ThreadPool() override = default;
@@ -1556,6 +1558,7 @@ private:
size_t base_thread_count_;
size_t max_thread_count_;
size_t max_queued_requests_;
time_t idle_timeout_sec_;
size_t idle_thread_count_;
bool shutdown_;
@@ -10304,8 +10307,10 @@ inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
}
// ThreadPool implementation
inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr)
: base_thread_count_(n), max_queued_requests_(mqr), idle_thread_count_(0),
inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr,
time_t idle_timeout_sec)
: base_thread_count_(n), max_queued_requests_(mqr),
idle_timeout_sec_(idle_timeout_sec), idle_thread_count_(0),
shutdown_(false) {
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
if (max_n != 0 && max_n < n) {
@@ -10415,9 +10420,9 @@ inline void ThreadPool::worker(bool is_dynamic) {
idle_thread_count_++;
if (is_dynamic) {
auto has_work = cond_.wait_for(
lock, std::chrono::seconds(CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT),
[&] { return !jobs_.empty() || shutdown_; });
auto has_work =
cond_.wait_for(lock, std::chrono::seconds(idle_timeout_sec_),
[&] { return !jobs_.empty() || shutdown_; });
if (!has_work) {
// Timed out with no work - exit this dynamic thread
idle_thread_count_--;

View File

@@ -13476,6 +13476,44 @@ TEST(TaskQueueTest, IncreaseAtomicIntegerWithQueueLimit) {
EXPECT_TRUE(queued_count >= qlimit);
}
TEST(TaskQueueTest, IdleTimeoutAtRuntime) {
// Use a short idle timeout so a dynamic thread spawns, times out and
// exits during the test, and the pool keeps working afterwards.
std::unique_ptr<TaskQueue> task_queue{new ThreadPool{
/*num_threads=*/1, /*max_threads=*/2, /*max_queued_requests=*/0,
/*idle_timeout_sec=*/1}};
std::atomic_uint count{0};
std::condition_variable cv;
std::mutex mtx;
bool release = false;
// Block the base thread so the second task spawns a dynamic thread.
EXPECT_TRUE(task_queue->enqueue([&] {
std::unique_lock<std::mutex> lock(mtx);
while (!release) {
cv.wait(lock);
}
count++;
}));
EXPECT_TRUE(task_queue->enqueue([&] { count++; }));
// Let the dynamic thread finish its task and exceed the idle timeout.
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
{
std::unique_lock<std::mutex> lock(mtx);
release = true;
}
cv.notify_all();
// The pool must still accept and run tasks after the dynamic thread exited.
EXPECT_TRUE(task_queue->enqueue([&] { count++; }));
task_queue->shutdown();
EXPECT_EQ(3u, count.load());
}
TEST(TaskQueueTest, MaxQueuedRequests) {
static constexpr unsigned int qlimit{3};
std::unique_ptr<TaskQueue> task_queue{new ThreadPool{1, 1, qlimit}};