LCOV - code coverage report
Current view: top level - src/base - thread_pool.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 224 229 97.8 %
Date: 2026-06-27 19:56:04 Functions: 34 35 97.1 %
Branches: 197 286 68.9 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * Copyright (C) 2026 by Thun Lu. All rights reserved.
       3                 :            :  * Author: Thun Lu <thun.lu@zohomail.cn>
       4                 :            :  * Repo:   https://github.com/thun-res/vlink
       5                 :            :  *  _    __   __      _           __
       6                 :            :  * | |  / /  / /     (_) ____    / /__
       7                 :            :  * | | / /  / /     / / / __ \  / //_/
       8                 :            :  * | |/ /  / /___  / / / / / / / ,<
       9                 :            :  * |___/  /_____/ /_/ /_/ /_/ /_/|_|
      10                 :            :  *
      11                 :            :  * Licensed under the Apache License, Version 2.0 (the "License");
      12                 :            :  * you may not use this file except in compliance with the License.
      13                 :            :  * You may obtain a copy of the License at
      14                 :            :  *
      15                 :            :  *     http://www.apache.org/licenses/LICENSE-2.0
      16                 :            :  *
      17                 :            :  * Unless required by applicable law or agreed to in writing, software
      18                 :            :  * distributed under the License is distributed on an "AS IS" BASIS,
      19                 :            :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      20                 :            :  * See the License for the specific language governing permissions and
      21                 :            :  * limitations under the License.
      22                 :            :  */
      23                 :            : 
      24                 :            : #include "./base/thread_pool.h"
      25                 :            : 
      26                 :            : #include <algorithm>
      27                 :            : #include <atomic>
      28                 :            : #include <deque>
      29                 :            : #include <mutex>
      30                 :            : #include <optional>
      31                 :            : #include <string>
      32                 :            : #include <thread>
      33                 :            : #include <tuple>
      34                 :            : #include <utility>
      35                 :            : #include <vector>
      36                 :            : 
      37                 :            : #include "./base/condition_variable.h"
      38                 :            : #include "./base/logger.h"
      39                 :            : #include "./base/memory_pool.h"
      40                 :            : #include "./base/memory_resource.h"
      41                 :            : #include "./base/mpmc_queue.h"
      42                 :            : #include "./base/utils.h"
      43                 :            : 
      44                 :            : namespace vlink {
      45                 :            : 
      46                 :            : static constexpr size_t kMaxTaskSize = 10000U;
      47                 :            : static constexpr int kMaxLockfreePushRetry = 32;
      48                 :            : 
      49                 :            : // ThreadPoolGlobal
      50                 :            : struct ThreadPoolGlobal final {
      51                 :            :   std::atomic<int> instance_index{0};
      52                 :            : 
      53                 :         67 :   static ThreadPoolGlobal& get() {
      54                 :            :     static ThreadPoolGlobal instance;
      55                 :            : 
      56                 :         67 :     return instance;
      57                 :            :   }
      58                 :            : 
      59                 :            :  private:
      60                 :            :   ThreadPoolGlobal() = default;
      61                 :            : };
      62                 :            : 
      63                 :            : // ThreadPool::Impl
      64                 :            : struct ThreadPool::Impl final {  // NOLINT(clang-analyzer-optin.performance.Padding)
      65                 :            : #ifdef VLINK_ENABLE_BASE_MEMORY_RESOURCE
      66                 :            :   using NormalTaskTuple = std::tuple<bool, ThreadPool::Callback>;
      67                 :            :   using LockfreeTaskTuple = std::tuple<ThreadPool::Callback>;
      68                 :            :   using NormalQueue = std::pmr::deque<NormalTaskTuple>;
      69                 :            :   using LockfreeQueue = MpmcQueue<LockfreeTaskTuple>;
      70                 :            : #else
      71                 :            :   using NormalTaskTuple = std::tuple<bool, ThreadPool::Callback>;
      72                 :            :   using LockfreeTaskTuple = std::tuple<ThreadPool::Callback>;
      73                 :            :   using NormalQueue = std::deque<NormalTaskTuple>;
      74                 :            :   using LockfreeQueue = MpmcQueue<LockfreeTaskTuple>;
      75                 :            : #endif
      76                 :            : 
      77                 :            :   alignas(64) std::atomic_bool quit_flag{false};
      78                 :            :   alignas(64) std::atomic_size_t lockfree_task_count{0U};
      79                 :            :   alignas(64) std::atomic_size_t lockfree_producer_count{0U};
      80                 :            : 
      81                 :            :   std::string name;
      82                 :            :   size_t thread_count{0};
      83                 :            :   ThreadPool::Type type{ThreadPool::kNormalType};
      84                 :            :   std::atomic<ThreadPool::Strategy> strategy{ThreadPool::kOptimizationStrategy};
      85                 :            :   std::vector<std::thread> threads;
      86                 :            : 
      87                 :            :   std::optional<NormalQueue> normal_queue;
      88                 :            :   std::optional<LockfreeQueue> lockfree_queue;
      89                 :            : 
      90                 :            :   ConditionVariable cv;
      91                 :            :   std::mutex mtx;
      92                 :            : };
      93                 :            : 
      94                 :            : // ThreadPool
      95                 :         40 : ThreadPool::ThreadPool(size_t thread_count) : impl_(MemoryResource::make_shared<Impl>()) {
      96                 :         40 :   impl_->name =
      97   [ +  -  +  - ]:        120 :       "ThreadPool_" + std::to_string(ThreadPoolGlobal::get().instance_index.fetch_add(1, std::memory_order_relaxed));
      98                 :         40 :   impl_->thread_count = thread_count;
      99                 :            : 
     100         [ +  - ]:         40 :   MemoryPool::global_instance();
     101                 :            : 
     102         [ +  - ]:         40 :   init();
     103                 :         40 : }
     104                 :            : 
     105                 :         27 : ThreadPool::ThreadPool(size_t thread_count, Type type) : impl_(MemoryResource::make_shared<Impl>()) {
     106                 :         27 :   impl_->name =
     107   [ +  -  +  - ]:         81 :       "ThreadPool_" + std::to_string(ThreadPoolGlobal::get().instance_index.fetch_add(1, std::memory_order_relaxed));
     108                 :         27 :   impl_->thread_count = thread_count;
     109                 :         27 :   impl_->type = type;
     110                 :            : 
     111         [ +  - ]:         27 :   MemoryPool::global_instance();
     112                 :            : 
     113         [ +  - ]:         27 :   init();
     114                 :         27 : }
     115                 :            : 
     116                 :         67 : ThreadPool::~ThreadPool() { shutdown(); }
     117                 :            : 
     118                 :          2 : void ThreadPool::set_name(const std::string& name) { impl_->name = name; }
     119                 :            : 
     120                 :          2 : const std::string& ThreadPool::get_name() const { return impl_->name; }
     121                 :            : 
     122                 :          3 : ThreadPool::Type ThreadPool::get_type() const { return impl_->type; }
     123                 :            : 
     124                 :          3 : ThreadPool::Strategy ThreadPool::get_strategy() const { return impl_->strategy.load(std::memory_order_acquire); }
     125                 :            : 
     126                 :         11 : void ThreadPool::set_strategy(Strategy strategy) { impl_->strategy.store(strategy, std::memory_order_release); }
     127                 :            : 
     128                 :        899 : bool ThreadPool::post_task(Callback&& callback) {
     129                 :        899 :   return push_task(std::move(callback), true, TaskOverflowPolicy::kUseDispatcherStrategy);
     130                 :            : }
     131                 :            : 
     132                 :         24 : TaskHandle ThreadPool::post_task_handle(Callback&& callback, const PostTaskOptions& options) {
     133         [ +  - ]:         24 :   auto handle = TaskHandle::make_task_handle(options.cancellation_token);
     134         [ +  - ]:         24 :   TaskHandle::mark_task_queued(handle);
     135                 :            : 
     136         [ +  + ]:         24 :   if (handle.state() == TaskExecutionState::kCancelled) {
     137                 :          2 :     return handle;
     138                 :            :   }
     139                 :            : 
     140   [ +  +  -  +  :         22 :   if VUNLIKELY (impl_->type == kLockfreeType && options.drop_policy == TaskDropPolicy::kProtected) {
                   -  + ]
     141                 :            :     CLOG_W("ThreadPool: TaskDropPolicy::kProtected is ignored by lock-free queues (%s).",  // LCOV_EXCL_LINE
     142                 :            :                                                                                            // GCOVR_EXCL_LINE
     143                 :            :            impl_->name.c_str());  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     144                 :            :   }
     145                 :            : 
     146         [ +  - ]:         22 :   auto tracked = TaskHandle::make_tracked_task(handle, std::move(callback));
     147                 :         22 :   const bool droppable = options.drop_policy == TaskDropPolicy::kDroppable;
     148                 :            : 
     149   [ +  -  +  +  :         22 :   if VUNLIKELY (!push_task(std::move(tracked), droppable, options.overflow_policy, &handle) && !handle.is_done()) {
             -  +  -  + ]
     150                 :            :     TaskHandle::mark_task_rejected(handle);  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     151                 :            :   }
     152                 :            : 
     153                 :         22 :   return handle;
     154                 :         22 : }
     155                 :            : 
     156                 :          4 : bool ThreadPool::drop_one_normal_task() {
     157         [ +  + ]:          5 :   for (auto iter = impl_->normal_queue->begin(); iter != impl_->normal_queue->end(); ++iter) {
     158         [ +  + ]:          4 :     if (std::get<0>(*iter)) {
     159         [ +  - ]:          3 :       impl_->normal_queue->erase(iter);
     160                 :          3 :       return true;
     161                 :            :     }
     162                 :            :   }
     163                 :            : 
     164                 :          1 :   return false;
     165                 :            : }
     166                 :            : 
     167                 :          1 : bool ThreadPool::drop_one_lockfree_task(bool keep_reserved) {
     168                 :          1 :   Impl::LockfreeTaskTuple task;
     169                 :            : 
     170         [ -  + ]:          1 :   if (!impl_->lockfree_queue->try_pop(task)) {
     171                 :            :     return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     172                 :            :   }
     173                 :            : 
     174         [ -  + ]:          1 :   if (!keep_reserved) {
     175                 :            :     release_lockfree_task();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     176                 :            :   }
     177                 :            : 
     178                 :          1 :   return true;
     179                 :          1 : }
     180                 :            : 
     181                 :        455 : bool ThreadPool::reserve_lockfree_task(bool* was_empty) {
     182                 :        455 :   auto count = impl_->lockfree_task_count.load(std::memory_order_acquire);
     183         [ +  - ]:        459 :   const auto max_count = get_max_task_count();
     184                 :            : 
     185         [ +  + ]:        488 :   while (count < max_count) {
     186         [ +  + ]:        940 :     if (impl_->lockfree_task_count.compare_exchange_weak(count, count + 1U, std::memory_order_acq_rel,
     187                 :            :                                                          std::memory_order_acquire)) {
     188         [ +  - ]:        436 :       if (was_empty != nullptr) {
     189                 :        436 :         *was_empty = count == 0U;
     190                 :            :       }
     191                 :            : 
     192                 :        436 :       return true;
     193                 :            :     }
     194                 :            :   }
     195                 :            : 
     196                 :         17 :   return false;
     197                 :            : }
     198                 :            : 
     199                 :          0 : void ThreadPool::release_lockfree_task() {
     200                 :          0 :   impl_->lockfree_task_count.fetch_sub(1U, std::memory_order_acq_rel);
     201                 :            : }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     202                 :            : 
     203                 :        435 : bool ThreadPool::push_lockfree_task(Callback&& callback) {
     204         [ +  - ]:        435 :   for (int retry = 0; retry < kMaxLockfreePushRetry; ++retry) {
     205         [ +  - ]:        435 :     if VLIKELY (impl_->lockfree_queue->try_push(std::forward_as_tuple(std::move(callback)))) {
     206                 :        436 :       return true;
     207                 :            :     }
     208                 :            : 
     209                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     210                 :            :     Utils::yield_cpu();
     211                 :            :   }
     212                 :            : 
     213                 :            :   CLOG_E("ThreadPool: Failed to push lockfree task after %d retries (%s).", kMaxLockfreePushRetry, impl_->name.c_str());
     214                 :            : 
     215                 :            :   return false;
     216                 :            :   // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     217                 :            : }
     218                 :            : 
     219                 :        923 : bool ThreadPool::push_task(Callback&& callback, bool droppable, TaskOverflowPolicy overflow_policy,
     220                 :            :                            const TaskHandle* submit_handle) {
     221                 :       1630 :   auto is_cancelled = [submit_handle]() -> bool {
     222   [ +  +  +  + ]:       1546 :     return submit_handle != nullptr && submit_handle->state() == TaskExecutionState::kCancelled;
     223                 :        923 :   };
     224                 :            : 
     225                 :         16 :   auto reject = [submit_handle]() -> bool {
     226   [ +  +  +  -  :          8 :     if (submit_handle != nullptr && !submit_handle->is_done()) {
                   +  + ]
     227                 :          4 :       TaskHandle::mark_task_rejected(*submit_handle);
     228                 :            :     }
     229                 :            : 
     230                 :          8 :     return false;
     231                 :        923 :   };
     232                 :            : 
     233         [ +  + ]:        923 :   if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     234         [ +  - ]:          4 :     return reject();
     235                 :            :   }
     236                 :            : 
     237                 :        919 :   bool is_full = false;
     238                 :        919 :   int retry_cnt = 0;
     239                 :            : 
     240         [ +  + ]:        919 :   if (impl_->type == kNormalType) {
     241         [ +  + ]:        556 :     do {
     242                 :            :       {
     243         [ +  - ]:        559 :         std::lock_guard lock(impl_->mtx);
     244                 :            : 
     245         [ +  + ]:        563 :         if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     246         [ +  - ]:          1 :           return reject();
     247                 :            :         }
     248                 :            : 
     249         [ +  + ]:        562 :         if VUNLIKELY (is_cancelled()) {
     250                 :          1 :           return false;
     251                 :            :         }
     252                 :            : 
     253         [ +  - ]:        561 :         is_full = impl_->normal_queue->size() >= get_max_task_count();
     254                 :            : 
     255         [ +  + ]:        561 :         if VLIKELY (!is_full) {
     256         [ +  - ]:        479 :           impl_->normal_queue->emplace_back(droppable, std::move(callback));
     257         [ +  + ]:         82 :         } else if (overflow_policy == TaskOverflowPolicy::kReject) {
     258         [ +  - ]:          1 :           return reject();
     259   [ +  +  +  -  :         81 :         } else if (impl_->strategy.load(std::memory_order_acquire) == kPopStrategy &&
                   +  + ]
     260                 :            :                    overflow_policy != TaskOverflowPolicy::kBlock) {
     261   [ +  -  +  + ]:          4 :           if (!drop_one_normal_task()) {
     262         [ +  - ]:          1 :             return reject();
     263                 :            :           }
     264                 :            : 
     265         [ +  - ]:          3 :           impl_->normal_queue->emplace_back(droppable, std::move(callback));
     266                 :          3 :           is_full = false;
     267                 :            : 
     268                 :          3 :           break;
     269                 :            :         }
     270      [ +  +  + ]:        563 :       }
     271                 :            : 
     272         [ +  + ]:        556 :       if VUNLIKELY (is_full) {
     273   [ +  +  -  +  :         77 :         if (impl_->strategy.load(std::memory_order_acquire) == kOptimizationStrategy &&
                   -  + ]
     274                 :            :             overflow_policy != TaskOverflowPolicy::kBlock) {
     275                 :            :           // LCOV_EXCL_START GCOVR_EXCL_START
     276                 :            :           if (++retry_cnt > 10) {
     277                 :            :             {
     278                 :            :               std::lock_guard lock(impl_->mtx);
     279                 :            : 
     280                 :            :               if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     281                 :            :                 return reject();
     282                 :            :               }
     283                 :            : 
     284                 :            :               if VUNLIKELY (is_cancelled()) {
     285                 :            :                 return false;
     286                 :            :               }
     287                 :            : 
     288                 :            :               if (!drop_one_normal_task()) {
     289                 :            :                 return reject();
     290                 :            :               }
     291                 :            : 
     292                 :            :               impl_->normal_queue->emplace_back(droppable, std::move(callback));
     293                 :            :               is_full = false;
     294                 :            :             }
     295                 :            : 
     296                 :            :             CLOG_W("ThreadPool: Task is full, removed top data (%s).", impl_->name.c_str());
     297                 :            :             break;
     298                 :            :           }
     299                 :            :           // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     300                 :            :         }
     301                 :            : 
     302         [ -  + ]:         77 :         if VUNLIKELY (is_cancelled()) {
     303                 :            :           return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     304                 :            :         }
     305                 :            : 
     306         [ +  - ]:         77 :         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     307                 :            :       }
     308                 :            :     } while (is_full);
     309         [ +  # ]:        431 :   } else if (impl_->type == kLockfreeType) {
     310                 :        430 :     bool notify_waiter = false;
     311                 :            : 
     312                 :            :     struct ProducerGuard final {
     313                 :        426 :       explicit ProducerGuard(Impl& impl) noexcept : impl_ref(impl) {
     314                 :        426 :         impl_ref.lockfree_producer_count.fetch_add(1U, std::memory_order_acq_rel);
     315                 :        426 :       }
     316                 :            : 
     317                 :        433 :       ~ProducerGuard() {
     318         [ +  + ]:        866 :         if (impl_ref.lockfree_producer_count.fetch_sub(1U, std::memory_order_acq_rel) == 1U) {
     319                 :        240 :           std::lock_guard lock(impl_ref.mtx);
     320                 :        240 :           impl_ref.cv.notify_all();
     321                 :        240 :         }
     322                 :        433 :       }
     323                 :            : 
     324                 :            :       Impl& impl_ref;
     325                 :        430 :     } producer_guard(*impl_);
     326                 :            : 
     327                 :       1306 :     auto push_reserved_lockfree_task = [this, &reject, &is_cancelled, &callback]() -> bool {
     328         [ -  + ]:        436 :       if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     329                 :            :         release_lockfree_task();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     330                 :            :         return reject();          // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     331                 :            :       }
     332                 :            : 
     333         [ -  + ]:        435 :       if VUNLIKELY (is_cancelled()) {
     334                 :            :         release_lockfree_task();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     335                 :            :         return false;             // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     336                 :            :       }
     337                 :            : 
     338         [ -  + ]:        435 :       if VUNLIKELY (!push_lockfree_task(std::move(callback))) {
     339                 :          0 :         release_lockfree_task();
     340                 :            :         return reject();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     341                 :            :       }
     342                 :            : 
     343                 :        435 :       return true;
     344                 :        433 :     };
     345                 :            : 
     346         [ +  - ]:         29 :     do {
     347         [ -  + ]:        462 :       if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     348                 :            :         return reject();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     349                 :            :       }
     350                 :            : 
     351         [ -  + ]:        454 :       if VUNLIKELY (is_cancelled()) {
     352                 :            :         return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     353                 :            :       }
     354                 :            : 
     355         [ +  - ]:        455 :       is_full = !reserve_lockfree_task(&notify_waiter);
     356                 :            : 
     357         [ +  + ]:        467 :       if VLIKELY (!is_full) {
     358   [ +  -  -  + ]:        436 :         if VUNLIKELY (!push_reserved_lockfree_task()) {
     359                 :            :           return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     360                 :            :         }
     361                 :            : 
     362                 :        433 :         break;
     363                 :            :       }
     364                 :            : 
     365         [ +  + ]:         31 :       if (overflow_policy == TaskOverflowPolicy::kReject) {
     366         [ +  - ]:          1 :         return reject();
     367                 :            :       }
     368                 :            : 
     369   [ +  +  +  -  :         30 :       if (impl_->strategy.load(std::memory_order_acquire) == kPopStrategy &&
                   +  + ]
     370                 :            :           overflow_policy != TaskOverflowPolicy::kBlock) {
     371   [ +  -  -  + ]:          1 :         if (!drop_one_lockfree_task(true)) {
     372                 :            :           return reject();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     373                 :            :         }
     374                 :            : 
     375   [ +  -  -  + ]:          1 :         if VUNLIKELY (!push_reserved_lockfree_task()) {
     376                 :            :           return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     377                 :            :         }
     378                 :            : 
     379                 :          1 :         is_full = false;
     380                 :          1 :         break;
     381                 :            :       }
     382                 :            : 
     383         [ +  - ]:         29 :       if VUNLIKELY (is_full) {
     384   [ -  +  -  -  :         29 :         if (impl_->strategy.load(std::memory_order_acquire) == kOptimizationStrategy &&
                   -  + ]
     385                 :            :             overflow_policy != TaskOverflowPolicy::kBlock) {
     386                 :            :           // LCOV_EXCL_START GCOVR_EXCL_START
     387                 :            :           if (++retry_cnt > 10) {
     388                 :            :             if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     389                 :            :               return reject();
     390                 :            :             }
     391                 :            : 
     392                 :            :             if VUNLIKELY (is_cancelled()) {
     393                 :            :               return false;
     394                 :            :             }
     395                 :            : 
     396                 :            :             if (!drop_one_lockfree_task(true)) {
     397                 :            :               return reject();
     398                 :            :             }
     399                 :            : 
     400                 :            :             if VUNLIKELY (!push_reserved_lockfree_task()) {
     401                 :            :               return false;
     402                 :            :             }
     403                 :            : 
     404                 :            :             is_full = false;
     405                 :            :             CLOG_W("ThreadPool: Task is full, removed top data (%s).", impl_->name.c_str());
     406                 :            :             break;
     407                 :            :           }
     408                 :            :           // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     409                 :            :         }
     410                 :            : 
     411         [ -  + ]:         29 :         if VUNLIKELY (is_cancelled()) {
     412                 :            :           return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     413                 :            :         }
     414                 :            : 
     415         [ +  - ]:         29 :         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     416                 :            :       }
     417                 :            :     } while (is_full);
     418                 :            : 
     419         [ +  + ]:        434 :     if (notify_waiter) {
     420                 :            :       // Pair the empty-to-non-empty transition with the wait mutex so workers cannot miss the notify.
     421                 :            :       {
     422         [ +  - ]:         13 :         std::lock_guard lock(impl_->mtx);
     423                 :         13 :       }
     424                 :         13 :       impl_->cv.notify_one();
     425                 :            :     }
     426         [ +  + ]:        436 :   }
     427                 :            : 
     428         [ +  + ]:        915 :   if (impl_->type != kLockfreeType) {
     429                 :        482 :     impl_->cv.notify_one();
     430                 :            :   }
     431                 :            : 
     432                 :        915 :   return !is_full;
     433                 :            : }
     434                 :            : 
     435                 :          2 : size_t ThreadPool::get_task_count() const {
     436         [ +  + ]:          2 :   if (impl_->type == kNormalType) {
     437         [ +  - ]:          1 :     std::lock_guard lock(impl_->mtx);
     438                 :          1 :     return impl_->normal_queue->size();
     439         [ +  - ]:          2 :   } else if (impl_->type == kLockfreeType) {
     440                 :          2 :     return impl_->lockfree_task_count.load(std::memory_order_acquire);
     441                 :            :   } else {
     442                 :            :     return 0U;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     443                 :            :   }
     444                 :            : }
     445                 :            : 
     446                 :         26 : bool ThreadPool::is_in_work_thread() const {
     447                 :         26 :   return std::any_of(impl_->threads.begin(), impl_->threads.end(),
     448                 :         81 :                      [](const std::thread& thread) -> bool { return thread.get_id() == std::this_thread::get_id(); });
     449                 :            : }
     450                 :            : 
     451                 :        889 : size_t ThreadPool::get_max_task_count() const { return kMaxTaskSize; }
     452                 :            : 
     453                 :        129 : bool ThreadPool::shutdown() {
     454                 :            :   {
     455         [ +  - ]:        129 :     std::lock_guard lock(impl_->mtx);
     456                 :            : 
     457         [ +  + ]:        129 :     if VUNLIKELY (impl_->quit_flag.load(std::memory_order_acquire)) {
     458                 :         64 :       return false;
     459                 :            :     }
     460                 :            : 
     461                 :         65 :     impl_->quit_flag.store(true, std::memory_order_release);
     462         [ +  + ]:        129 :   }
     463                 :            : 
     464         [ +  + ]:         65 :   if (impl_->type == kLockfreeType) {
     465         [ +  - ]:         10 :     std::unique_lock lock(impl_->mtx);
     466                 :         30 :     impl_->cv.wait(lock, [this] { return impl_->lockfree_producer_count.load(std::memory_order_acquire) == 0U; });
     467                 :         10 :   }
     468                 :            : 
     469                 :         65 :   impl_->cv.notify_all();
     470                 :            : 
     471                 :         65 :   const auto self_id = std::this_thread::get_id();
     472                 :            : 
     473         [ +  + ]:        190 :   for (auto& thread : impl_->threads) {
     474         [ +  - ]:        125 :     if (thread.joinable()) {
     475         [ +  + ]:        125 :       if (thread.get_id() == self_id) {
     476         [ +  - ]:          1 :         thread.detach();
     477                 :            :       } else {
     478         [ +  - ]:        124 :         thread.join();
     479                 :            :       }
     480                 :            :     }
     481                 :            :   }
     482                 :            : 
     483                 :         65 :   return true;
     484                 :            : }
     485                 :            : 
     486                 :         67 : void ThreadPool::init() {
     487         [ +  + ]:         67 :   if (impl_->type == kNormalType) {
     488                 :         57 :     impl_->normal_queue.emplace();
     489         [ +  - ]:         10 :   } else if (impl_->type == kLockfreeType) {
     490         [ +  - ]:         10 :     const auto max_task_count = get_max_task_count();  // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)
     491         [ +  - ]:         10 :     impl_->lockfree_queue.emplace(max_task_count);
     492                 :         10 :     impl_->lockfree_task_count.store(0U, std::memory_order_release);
     493                 :            :   }
     494                 :            : 
     495         [ +  + ]:         67 :   if VUNLIKELY (impl_->thread_count == 0) {
     496   [ +  -  +  - ]:          2 :     VLOG_E("ThreadPool: Thread count is zero.");
     497                 :          1 :     impl_->quit_flag.store(true, std::memory_order_release);
     498                 :          1 :     return;
     499                 :            :   }
     500                 :            : 
     501                 :         66 :   impl_->threads.reserve(impl_->thread_count);
     502                 :            : 
     503         [ +  + ]:        192 :   for (size_t i = 0; i < impl_->thread_count; ++i) {
     504         [ +  + ]:        126 :     if (impl_->type == kNormalType) {
     505                 :        104 :       auto impl = impl_;
     506                 :        582 :       std::thread thread([impl] {
     507                 :            :         for (;;) {
     508                 :        582 :           Callback task;
     509                 :            : 
     510                 :            :           {
     511         [ +  - ]:        582 :             std::unique_lock lock(impl->mtx);
     512                 :        583 :             impl->cv.wait(lock, [impl] {
     513   [ +  +  +  + ]:        878 :               return !impl->normal_queue->empty() || impl->quit_flag.load(std::memory_order_acquire);
     514                 :            :             });
     515                 :            : 
     516   [ +  +  +  -  :        582 :             if VUNLIKELY (impl->normal_queue->empty() && impl->quit_flag.load(std::memory_order_acquire)) {
                   +  + ]
     517                 :        103 :               break;
     518                 :            :             }
     519                 :            : 
     520                 :        479 :             task = std::move(std::get<1>(impl->normal_queue->front()));
     521                 :            : 
     522                 :        479 :             impl->normal_queue->pop_front();
     523         [ +  + ]:        582 :           }
     524                 :            : 
     525         [ +  - ]:        479 :           if VLIKELY (task) {
     526         [ +  - ]:        479 :             task();
     527                 :            :           }
     528         [ +  + ]:       1060 :         }
     529         [ +  - ]:        207 :       });
     530                 :            : 
     531         [ +  - ]:        104 :       impl_->threads.emplace_back(std::move(thread));
     532         [ +  - ]:        126 :     } else if (impl_->type == kLockfreeType) {
     533                 :         22 :       auto impl = impl_;
     534                 :        482 :       std::thread thread([impl] {
     535                 :            :         for (;;) {
     536                 :        482 :           Impl::LockfreeTaskTuple task_tuple;
     537                 :            : 
     538                 :        482 :           const bool has_task = impl->lockfree_queue->try_pop(task_tuple);
     539                 :            : 
     540         [ +  + ]:        482 :           if (!has_task) {
     541   [ +  +  +  -  :         68 :             if VUNLIKELY (impl->quit_flag.load(std::memory_order_acquire) &&
                   +  + ]
     542                 :            :                           impl->lockfree_task_count.load(std::memory_order_acquire) == 0U) {
     543         [ +  - ]:         44 :               if (impl->lockfree_producer_count.load(std::memory_order_acquire) ==
     544                 :            :                   0U) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     545                 :         22 :                 break;
     546                 :            :               }
     547                 :            : 
     548                 :            :               std::this_thread::yield();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     549                 :         24 :               continue;
     550                 :            :             }
     551                 :            : 
     552         [ -  + ]:         48 :             if (impl->lockfree_task_count.load(std::memory_order_acquire) != 0U) {
     553                 :          0 :               std::this_thread::yield();
     554                 :          0 :               continue;
     555                 :            :             }
     556                 :            : 
     557         [ +  - ]:         24 :             std::unique_lock lock(impl->mtx);
     558                 :         24 :             impl->cv.wait(lock, [impl] {
     559   [ +  +  +  + ]:        168 :               return impl->lockfree_task_count.load(std::memory_order_acquire) != 0U ||
     560                 :        110 :                      impl->quit_flag.load(std::memory_order_acquire);
     561                 :            :             });
     562                 :            : 
     563                 :         24 :             continue;
     564                 :         24 :           }
     565                 :            : 
     566                 :        436 :           impl->lockfree_task_count.fetch_sub(1U, std::memory_order_acq_rel);
     567                 :            : 
     568                 :        436 :           auto& task = std::get<0>(task_tuple);
     569                 :            : 
     570         [ +  - ]:        436 :           if VLIKELY (task) {
     571         [ +  - ]:        436 :             task();
     572                 :            :           }
     573      [ +  +  + ]:        942 :         }
     574         [ +  - ]:         44 :       });
     575                 :            : 
     576         [ +  - ]:         22 :       impl_->threads.emplace_back(std::move(thread));
     577                 :         22 :     }
     578                 :            :   }
     579                 :            : }
     580                 :            : 
     581                 :            : }  // namespace vlink

Generated by: LCOV version 1.14