LCOV - code coverage report
Current view: top level - src/base - wheel_timer.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 193 195 99.0 %
Date: 2026-06-27 19:56:04 Functions: 22 22 100.0 %
Branches: 146 228 64.0 %

           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/wheel_timer.h"
      25                 :            : 
      26                 :            : #include <atomic>
      27                 :            : #include <limits>
      28                 :            : #include <list>
      29                 :            : #include <mutex>
      30                 :            : #include <optional>
      31                 :            : #include <thread>
      32                 :            : #include <unordered_map>
      33                 :            : #include <utility>
      34                 :            : #include <vector>
      35                 :            : 
      36                 :            : #include "./base/condition_variable.h"
      37                 :            : #include "./base/logger.h"
      38                 :            : #include "./base/memory_pool.h"
      39                 :            : #include "./base/memory_resource.h"
      40                 :            : 
      41                 :            : namespace vlink {
      42                 :            : 
      43                 :            : // WheelTimer::Impl
      44                 :            : struct WheelTimer::Impl final {  // NOLINT(clang-analyzer-optin.performance.Padding)
      45                 :            :   // Handler
      46                 :            :   struct Handler final {
      47                 :            :     WheelTimer::Key key{-1};
      48                 :            :     uint32_t remaining_rounds{0};
      49                 :            :     WheelTimer::Callback callback;
      50                 :            :     uint32_t repeat_interval_ms{0};
      51                 :            : 
      52                 :        118 :     Handler(WheelTimer::Key _key, uint32_t _rounds, WheelTimer::Callback&& _callback, uint32_t _repeat_ms = 0)
      53                 :        118 :         : key(_key), remaining_rounds(_rounds), callback(std::move(_callback)), repeat_interval_ms(_repeat_ms) {}
      54                 :            : 
      55                 :            :     Handler(const Handler&) = default;
      56                 :            : 
      57                 :         28 :     Handler(Handler&&) = default;
      58                 :            : 
      59                 :            :     Handler& operator=(const Handler&) = default;
      60                 :            : 
      61                 :            :     Handler& operator=(Handler&&) = default;
      62                 :            :   };
      63                 :            : 
      64                 :            :   alignas(64) std::atomic_bool stop_flag{false};
      65                 :            :   alignas(64) std::atomic_bool paused_flag{false};
      66                 :            :   alignas(64) std::atomic_bool is_running{false};
      67                 :            : 
      68                 :            :   std::atomic<uint32_t> catchup_limit{0};
      69                 :            :   std::atomic<WheelTimer::Key> next_key{1};
      70                 :            : 
      71                 :            :   uint32_t slots{0};
      72                 :            :   uint32_t interval_ms{5};
      73                 :            : 
      74                 :            : #ifdef VLINK_ENABLE_BASE_MEMORY_RESOURCE
      75                 :            :   std::optional<std::pmr::vector<std::pmr::list<Handler>>> wheels;
      76                 :            : #else
      77                 :            :   std::optional<std::vector<std::list<Handler>>> wheels;
      78                 :            : #endif
      79                 :            : 
      80                 :            :   uint32_t current_slot{0};
      81                 :            : 
      82                 :            :   std::thread worker_thread;
      83                 :            : 
      84                 :            :   std::mutex mtx;
      85                 :            :   std::mutex lifecycle_mtx;
      86                 :            :   ConditionVariable cv;
      87                 :            : 
      88                 :            :   std::unordered_map<WheelTimer::Key, std::pair<uint32_t, std::list<Handler>::iterator>> timer_index;
      89                 :            : 
      90                 :            :   void run();
      91                 :            : };
      92                 :            : 
      93                 :            : // WheelTimer
      94                 :         34 : WheelTimer::WheelTimer(uint32_t slots, uint32_t interval_ms) : impl_(MemoryResource::make_shared<Impl>()) {
      95   [ +  +  +  +  :         34 :   if VUNLIKELY (slots == 0 || interval_ms == 0) {
                   +  + ]
      96   [ +  -  -  + ]:          4 :     VLOG_F("WheelTimer: Slots and interval_ms must be greater than 0.");
      97                 :            :   }
      98                 :            : 
      99         [ +  - ]:         32 :   MemoryPool::global_instance();
     100                 :            : 
     101                 :            : #ifdef VLINK_ENABLE_BASE_MEMORY_RESOURCE
     102         [ +  - ]:         32 :   impl_->wheels.emplace(&MemoryResource::global_instance());
     103                 :            : #else
     104                 :            :   impl_->wheels.emplace();
     105                 :            : #endif
     106                 :            : 
     107         [ +  - ]:         32 :   impl_->slots = (slots == 0) ? 1U : slots;
     108         [ +  - ]:         32 :   impl_->interval_ms = (interval_ms == 0) ? 1U : interval_ms;
     109         [ +  - ]:         32 :   impl_->wheels->resize(impl_->slots);
     110                 :         34 : }
     111                 :            : 
     112                 :         32 : WheelTimer::~WheelTimer() {
     113                 :         32 :   stop();
     114                 :         32 :   impl_->wheels.reset();
     115                 :         32 : }
     116                 :            : 
     117                 :         32 : void WheelTimer::start() {
     118         [ +  - ]:         32 :   std::lock_guard lifecycle_lock(impl_->lifecycle_mtx);
     119                 :            : 
     120                 :            :   {
     121         [ +  - ]:         32 :     std::lock_guard lock(impl_->mtx);
     122                 :            : 
     123         [ +  + ]:         32 :     if VUNLIKELY (impl_->is_running.load(std::memory_order_acquire)) {
     124   [ +  -  +  - ]:          2 :       VLOG_W("WheelTimer: Timer is already running.");
     125                 :          1 :       return;
     126                 :            :     }
     127         [ +  + ]:         32 :   }
     128                 :            : 
     129         [ -  + ]:         31 :   if (impl_->worker_thread.joinable()) {
     130                 :            :     impl_->worker_thread.join();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     131                 :            :   }
     132                 :            : 
     133                 :            :   {
     134         [ +  - ]:         31 :     std::lock_guard lock(impl_->mtx);
     135                 :         31 :     impl_->stop_flag.store(false, std::memory_order_release);
     136                 :         31 :     impl_->is_running.store(true, std::memory_order_release);
     137                 :         31 :   }
     138                 :            : 
     139                 :            :   try {
     140                 :         31 :     auto impl_copy = impl_;
     141         [ +  - ]:         62 :     impl_->worker_thread = std::thread([impl_copy]() { impl_copy->run(); });
     142         [ -  - ]:         31 :   } catch (std::exception&) {
     143                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     144                 :            :     {
     145                 :            :       std::lock_guard lock(impl_->mtx);
     146                 :            :       impl_->is_running.store(false, std::memory_order_release);
     147                 :            :     }
     148                 :            : 
     149                 :            :     throw;
     150                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     151                 :          0 :   }
     152         [ +  + ]:         32 : }
     153                 :            : 
     154                 :         64 : void WheelTimer::stop() {
     155                 :            :   {
     156         [ +  - ]:         64 :     std::lock_guard lock(impl_->mtx);
     157                 :         64 :     impl_->stop_flag.store(true, std::memory_order_release);
     158                 :         64 :   }
     159                 :            : 
     160         [ +  - ]:         64 :   wakeup();
     161                 :            : 
     162                 :            :   const bool called_from_worker =
     163   [ +  +  +  + ]:         64 :       impl_->worker_thread.joinable() && impl_->worker_thread.get_id() == std::this_thread::get_id();
     164                 :            : 
     165         [ +  + ]:         64 :   if (called_from_worker) {
     166         [ +  - ]:          1 :     if (impl_->lifecycle_mtx.try_lock()) {
     167         [ +  - ]:          1 :       if (impl_->worker_thread.joinable()) {
     168         [ +  - ]:          1 :         impl_->worker_thread.detach();
     169                 :            :       }
     170                 :            : 
     171                 :          1 :       impl_->lifecycle_mtx.unlock();
     172                 :            :     }
     173                 :            : 
     174                 :          1 :     return;
     175                 :            :   }
     176                 :            : 
     177         [ +  - ]:         63 :   std::lock_guard lifecycle_lock(impl_->lifecycle_mtx);
     178                 :            : 
     179         [ +  + ]:         63 :   if (impl_->worker_thread.joinable()) {
     180         [ +  - ]:         30 :     impl_->worker_thread.join();
     181                 :            :   } else {
     182         [ +  - ]:         33 :     std::unique_lock lock(impl_->mtx);
     183                 :         66 :     impl_->cv.wait(lock, [this]() { return !impl_->is_running.load(std::memory_order_acquire); });
     184                 :         33 :   }
     185                 :         63 : }
     186                 :            : 
     187                 :          4 : void WheelTimer::pause() {
     188         [ +  - ]:          4 :   std::lock_guard lock(impl_->mtx);
     189                 :          4 :   impl_->paused_flag.store(true, std::memory_order_release);
     190                 :          4 : }
     191                 :            : 
     192                 :          3 : void WheelTimer::resume() {
     193         [ +  - ]:          3 :   std::unique_lock lock(impl_->mtx);
     194                 :          3 :   impl_->paused_flag.store(false, std::memory_order_release);
     195                 :            : 
     196         [ +  - ]:          3 :   lock.unlock();
     197                 :            : 
     198         [ +  - ]:          3 :   wakeup();
     199                 :          3 : }
     200                 :            : 
     201                 :        172 : void WheelTimer::wakeup() { impl_->cv.notify_one(); }
     202                 :            : 
     203                 :          6 : bool WheelTimer::is_running() const { return impl_->is_running.load(std::memory_order_acquire); }
     204                 :            : 
     205                 :         92 : WheelTimer::Key WheelTimer::add(uint32_t timeout_ms, Callback&& callback, uint32_t repeat_ms) {
     206         [ +  + ]:         92 :   if VUNLIKELY (timeout_ms == 0) {
     207   [ +  -  +  - ]:          2 :     VLOG_E("WheelTimer: Timeout must be greater than 0.");
     208                 :          1 :     return -1;
     209                 :            :   }
     210                 :            : 
     211         [ +  + ]:         91 :   if VUNLIKELY (!callback) {
     212   [ +  -  +  - ]:          2 :     VLOG_E("WheelTimer: Callback must be non-empty.");
     213                 :          1 :     return -1;
     214                 :            :   }
     215                 :            : 
     216         [ +  - ]:         90 :   std::lock_guard lock(impl_->mtx);
     217                 :            : 
     218                 :         90 :   uint32_t interval = impl_->interval_ms;
     219                 :         90 :   uint32_t slots = impl_->slots;
     220                 :         90 :   uint32_t current_slot = impl_->current_slot;
     221                 :            : 
     222                 :         90 :   uint64_t ticks = (static_cast<uint64_t>(timeout_ms) + interval - 1) / interval;
     223                 :            : 
     224         [ -  + ]:         90 :   if VUNLIKELY (ticks == 0) {
     225                 :            :     ticks = 1;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     226                 :            :   }
     227                 :            : 
     228                 :         90 :   uint64_t max_rounds = std::numeric_limits<uint32_t>::max();
     229                 :         90 :   uint64_t rounds64 = ticks / slots;
     230                 :            : 
     231         [ -  + ]:         90 :   if VUNLIKELY (rounds64 > max_rounds) {
     232                 :            :     VLOG_E("WheelTimer: Timeout too large (rounds overflow).");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     233                 :            :     return -1;                                                   // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     234                 :            :   }
     235                 :            : 
     236                 :         90 :   auto ticks_mod = static_cast<uint32_t>(ticks % slots);
     237                 :         90 :   auto rounds = static_cast<uint32_t>(rounds64);
     238                 :         90 :   uint32_t slot = (current_slot + ticks_mod) % slots;
     239                 :            : 
     240                 :         90 :   WheelTimer::Key key = impl_->next_key.fetch_add(1, std::memory_order_relaxed);
     241                 :            : 
     242         [ -  + ]:         90 :   if VUNLIKELY (key <= 0) {
     243                 :            :     impl_->next_key.store(1, std::memory_order_relaxed);            // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     244                 :            :     key = impl_->next_key.fetch_add(1, std::memory_order_relaxed);  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     245                 :            :   }
     246                 :            : 
     247                 :         90 :   int probe = 0;
     248                 :            : 
     249   [ +  -  -  +  :         90 :   while (impl_->timer_index.find(key) != impl_->timer_index.end() && probe < 8) {
             -  -  -  + ]
     250                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     251                 :            :     key = impl_->next_key.fetch_add(1, std::memory_order_relaxed);
     252                 :            : 
     253                 :            :     if (key <= 0) {
     254                 :            :       impl_->next_key.store(1, std::memory_order_relaxed);
     255                 :            :       key = impl_->next_key.fetch_add(1, std::memory_order_relaxed);
     256                 :            :     }
     257                 :            : 
     258                 :            :     ++probe;
     259                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     260                 :            :   }
     261                 :            : 
     262   [ +  -  -  + ]:         90 :   if VUNLIKELY (impl_->timer_index.find(key) != impl_->timer_index.end()) {
     263                 :            :     VLOG_E("WheelTimer: Failed to allocate a unique key.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     264                 :            :     return -1;                                               // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     265                 :            :   }
     266                 :            : 
     267                 :         90 :   auto& slot_list = (*impl_->wheels)[slot];
     268         [ +  - ]:         90 :   slot_list.emplace_back(key, rounds, std::move(callback), repeat_ms);
     269                 :            : 
     270         [ +  - ]:         90 :   auto it = std::prev(slot_list.end());
     271         [ +  - ]:         90 :   impl_->timer_index[key] = {slot, it};
     272                 :            : 
     273         [ +  - ]:         90 :   wakeup();
     274                 :            : 
     275                 :         90 :   return key;
     276                 :         90 : }
     277                 :            : 
     278                 :         16 : bool WheelTimer::remove(WheelTimer::Key key) {
     279                 :            :   {
     280         [ +  - ]:         16 :     std::lock_guard lock(impl_->mtx);
     281                 :            : 
     282         [ +  - ]:         16 :     auto it = impl_->timer_index.find(key);
     283                 :            : 
     284         [ +  + ]:         16 :     if VUNLIKELY (it == impl_->timer_index.end()) {
     285                 :          2 :       return false;
     286                 :            :     }
     287                 :            : 
     288                 :         14 :     auto& slot_list = (*impl_->wheels)[it->second.first];
     289                 :         14 :     slot_list.erase(it->second.second);
     290         [ +  - ]:         14 :     impl_->timer_index.erase(it);
     291         [ +  + ]:         16 :   }
     292                 :            : 
     293                 :         14 :   wakeup();
     294                 :            : 
     295                 :         14 :   return true;
     296                 :            : }
     297                 :            : 
     298                 :          5 : uint32_t WheelTimer::get_remaining_time(Key key) const {
     299         [ +  - ]:          5 :   std::lock_guard lock(impl_->mtx);
     300                 :            : 
     301         [ +  - ]:          5 :   auto it = impl_->timer_index.find(key);
     302                 :            : 
     303         [ +  + ]:          5 :   if (it == impl_->timer_index.end()) {
     304                 :          2 :     return 0;
     305                 :            :   }
     306                 :            : 
     307                 :          3 :   uint32_t slot = it->second.first;
     308                 :          3 :   uint32_t current_slot = impl_->current_slot;
     309                 :          3 :   uint32_t delta_slot = (slot + impl_->slots - current_slot) % impl_->slots;
     310                 :          3 :   uint32_t rounds = it->second.second->remaining_rounds;
     311                 :            : 
     312                 :          3 :   uint64_t total_ticks = static_cast<uint64_t>(rounds) * impl_->slots + delta_slot;
     313                 :          3 :   uint64_t total_ms = total_ticks * impl_->interval_ms;
     314                 :            : 
     315         [ -  + ]:          3 :   return (total_ms > std::numeric_limits<uint32_t>::max()) ? std::numeric_limits<uint32_t>::max()
     316                 :          3 :                                                            : static_cast<uint32_t>(total_ms);
     317                 :          5 : }
     318                 :            : 
     319                 :          3 : void WheelTimer::set_catchup_limit(uint32_t max_slots_to_catch_up) {
     320                 :          3 :   impl_->catchup_limit.store(max_slots_to_catch_up, std::memory_order_relaxed);
     321                 :          3 : }
     322                 :            : 
     323                 :         31 : void WheelTimer::Impl::run() {
     324                 :            : #ifdef VLINK_ENABLE_BASE_MEMORY_RESOURCE
     325                 :            :   std::pmr::vector<std::pair<WheelTimer::Key, WheelTimer::Callback>> callbacks_to_execute(
     326         [ +  - ]:         31 :       &MemoryResource::global_instance());
     327                 :            : #else
     328                 :            :   std::vector<std::pair<WheelTimer::Key, WheelTimer::Callback>> callbacks_to_execute;
     329                 :            : #endif
     330                 :            : 
     331                 :         31 :   auto interval = std::chrono::milliseconds(interval_ms);
     332                 :            : 
     333                 :         31 :   auto next_tick = std::chrono::steady_clock::now();
     334                 :            : 
     335                 :            :   for (;;) {
     336         [ +  - ]:        253 :     std::unique_lock lock(mtx);
     337                 :            : 
     338         [ +  + ]:        253 :     if VUNLIKELY (stop_flag.load(std::memory_order_acquire)) {
     339                 :          8 :       break;
     340                 :            :     }
     341                 :            : 
     342   [ +  +  +  +  :        249 :     while (paused_flag.load(std::memory_order_acquire) && !stop_flag.load(std::memory_order_acquire)) {
                   +  + ]
     343                 :          4 :       cv.wait(lock);
     344                 :            :     }
     345                 :            : 
     346         [ +  + ]:        245 :     if VUNLIKELY (stop_flag.load(std::memory_order_acquire)) {
     347                 :          1 :       break;
     348                 :            :     }
     349                 :            : 
     350                 :        244 :     auto now = std::chrono::steady_clock::now();
     351                 :            : 
     352   [ +  -  +  + ]:        244 :     if (now < next_tick) {
     353                 :        215 :       cv.wait_until(lock, next_tick, [this]() -> bool {
     354   [ +  +  +  + ]:        437 :         return stop_flag.load(std::memory_order_acquire) || paused_flag.load(std::memory_order_acquire);
     355                 :            :       });
     356                 :            : 
     357         [ +  + ]:        215 :       if VUNLIKELY (stop_flag.load(std::memory_order_acquire)) {
     358                 :         22 :         break;
     359                 :            :       }
     360                 :            : 
     361                 :        193 :       now = std::chrono::steady_clock::now();
     362                 :            :     }
     363                 :            : 
     364                 :        222 :     constexpr int64_t kStaleTickResetIntervals = 10;
     365                 :            : 
     366   [ +  -  +  -  :        222 :     if VUNLIKELY (next_tick + interval * kStaleTickResetIntervals < now) {
             +  -  -  + ]
     367                 :            :       next_tick = now + interval;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     368                 :            :     }
     369                 :            : 
     370                 :        222 :     uint32_t advanced = 0;
     371                 :        222 :     uint32_t catchup_limit_snapshot = catchup_limit.load(std::memory_order_relaxed);
     372                 :            : 
     373   [ +  -  +  +  :        693 :     while (now >= next_tick && !stop_flag.load(std::memory_order_acquire) &&
             +  -  +  + ]
     374         [ +  + ]:        238 :            !paused_flag.load(std::memory_order_acquire)) {
     375                 :        234 :       auto& timers = (*wheels)[current_slot];
     376                 :            : 
     377         [ +  + ]:        335 :       for (auto it = timers.begin(); it != timers.end();) {
     378         [ +  + ]:        101 :         if VLIKELY (it->remaining_rounds > 0) {
     379                 :          6 :           --(it->remaining_rounds);
     380                 :          6 :           ++it;
     381                 :            :         } else {
     382         [ +  + ]:         95 :           if (it->repeat_interval_ms > 0) {
     383         [ +  - ]:         28 :             callbacks_to_execute.emplace_back(it->key, it->callback);
     384                 :            : 
     385                 :         28 :             uint64_t repeat_ticks = (static_cast<uint64_t>(it->repeat_interval_ms) + interval_ms - 1) / interval_ms;
     386                 :            : 
     387         [ -  + ]:         28 :             if VUNLIKELY (repeat_ticks == 0) {
     388                 :            :               repeat_ticks = 1;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     389                 :            :             }
     390                 :            : 
     391                 :         28 :             uint64_t rounds64 = repeat_ticks / slots;
     392                 :            : 
     393         [ -  + ]:         28 :             if VUNLIKELY (rounds64 > std::numeric_limits<uint32_t>::max()) {
     394                 :            :               // LCOV_EXCL_START GCOVR_EXCL_START
     395                 :            :               VLOG_E("WheelTimer: Repeat interval too large.");
     396                 :            : 
     397                 :            :               timer_index.erase(it->key);
     398                 :            :               it = timers.erase(it);
     399                 :            : 
     400                 :            :               continue;
     401                 :            :               // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     402                 :          0 :             }
     403                 :            : 
     404                 :         28 :             auto repeat_ticks_mod = static_cast<uint32_t>(repeat_ticks % slots);
     405                 :         28 :             auto new_rounds = static_cast<uint32_t>(rounds64);
     406                 :         28 :             auto new_slot = (current_slot + repeat_ticks_mod) % slots;
     407                 :            : 
     408                 :         28 :             Handler new_handler(it->key, new_rounds, std::move(it->callback), it->repeat_interval_ms);
     409                 :         28 :             auto& new_list = (*wheels)[new_slot];
     410                 :            : 
     411         [ +  - ]:         28 :             new_list.emplace_back(std::move(new_handler));
     412                 :            : 
     413         [ +  - ]:         28 :             auto new_it = std::prev(new_list.end());
     414                 :            : 
     415         [ +  - ]:         28 :             timer_index[it->key] = {new_slot, new_it};
     416                 :            : 
     417                 :         28 :             it = timers.erase(it);
     418                 :         28 :           } else {
     419         [ +  - ]:         67 :             callbacks_to_execute.emplace_back(it->key, std::move(it->callback));
     420         [ +  - ]:         67 :             timer_index.erase(it->key);
     421                 :         67 :             it = timers.erase(it);
     422                 :            :           }
     423                 :            :         }
     424                 :            :       }
     425                 :            : 
     426                 :        234 :       current_slot = (current_slot + 1) % slots;
     427                 :            : 
     428   [ +  -  +  - ]:        234 :       next_tick += interval;
     429                 :            : 
     430         [ +  + ]:        234 :       if (catchup_limit_snapshot > 0) {
     431         [ +  + ]:         45 :         if (++advanced >= catchup_limit_snapshot) {
     432                 :          1 :           break;
     433                 :            :         }
     434                 :            :       }
     435                 :            :     }
     436                 :            : 
     437                 :        222 :     now = std::chrono::steady_clock::now();
     438                 :            : 
     439   [ +  -  +  -  :        222 :     if VUNLIKELY (next_tick + interval * kStaleTickResetIntervals < now) {
             +  -  -  + ]
     440                 :            :       next_tick = now + interval;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     441                 :            :     }
     442                 :            : 
     443                 :            : #ifdef VLINK_ENABLE_BASE_MEMORY_RESOURCE
     444         [ +  - ]:        222 :     decltype(callbacks_to_execute) pending_callbacks(&MemoryResource::global_instance());
     445                 :            : #else
     446                 :            :     decltype(callbacks_to_execute) pending_callbacks;
     447                 :            : #endif
     448                 :            : 
     449                 :        222 :     pending_callbacks.swap(callbacks_to_execute);
     450                 :            : 
     451         [ +  - ]:        222 :     lock.unlock();
     452                 :            : 
     453         [ +  + ]:        317 :     for (const auto& [key, callback] : pending_callbacks) {
     454         [ +  - ]:         95 :       callback(key);
     455                 :            :     }
     456         [ +  + ]:        475 :   }
     457                 :            : 
     458                 :            :   {
     459         [ +  - ]:         31 :     std::lock_guard lock(mtx);
     460                 :         31 :     is_running.store(false, std::memory_order_release);
     461                 :         31 :     paused_flag.store(false, std::memory_order_release);
     462                 :         31 :     current_slot = 0;
     463                 :            : 
     464                 :            : #ifdef VLINK_ENABLE_BASE_MEMORY_RESOURCE
     465         [ +  - ]:         31 :     wheels.emplace(&MemoryResource::global_instance());
     466                 :            : #else
     467                 :            :     wheels.emplace();
     468                 :            : #endif
     469                 :            : 
     470         [ +  - ]:         31 :     wheels->resize(slots);
     471                 :            : 
     472                 :         31 :     timer_index.clear();
     473                 :         31 :   }
     474                 :            : 
     475                 :         31 :   cv.notify_all();
     476                 :         31 : }
     477                 :            : 
     478                 :            : }  // namespace vlink

Generated by: LCOV version 1.14