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/multi_loop.h" 25 : : 26 : : #include <atomic> 27 : : #include <chrono> 28 : : #include <memory> 29 : : #include <mutex> 30 : : #include <optional> 31 : : #include <utility> 32 : : 33 : : #include "./base/condition_variable.h" 34 : : #include "./base/logger.h" 35 : : #include "./base/thread_pool.h" 36 : : 37 : : namespace vlink { 38 : : 39 : : // MultiLoopGlobal 40 : : struct MultiLoopGlobal final { 41 : : std::atomic<int> instance_index{0}; 42 : : 43 : 24 : static MultiLoopGlobal& get() { 44 : : static MultiLoopGlobal instance; 45 : : 46 : 24 : return instance; 47 : : } 48 : : 49 : : private: 50 : : MultiLoopGlobal() = default; 51 : : }; 52 : : 53 : : // MultiLoop::Impl 54 : : struct MultiLoop::Impl final { 55 : : mutable std::mutex pool_mtx; 56 : : mutable std::mutex idle_mtx; 57 : : ConditionVariable idle_cv; 58 : : std::atomic_size_t pending_tasks{0U}; 59 : : std::optional<ThreadPool> thread_pool; 60 : : size_t thread_num{0}; 61 : : }; 62 : : 63 : : // MultiLoop 64 [ + - ]: 19 : MultiLoop::MultiLoop(size_t thread_num) : impl_(std::make_unique<Impl>()) { 65 : 19 : impl_->thread_num = thread_num; 66 : : 67 [ + - + - ]: 19 : set_name("MultiLoop_" + 68 [ + - ]: 57 : std::to_string(MultiLoopGlobal::get().instance_index.fetch_add(1, std::memory_order_relaxed))); 69 : 19 : } 70 : : 71 [ + - ]: 5 : MultiLoop::MultiLoop(size_t thread_num, Type type) : MessageLoop(type), impl_(std::make_unique<Impl>()) { 72 : 5 : impl_->thread_num = thread_num; 73 : : 74 [ + - + - ]: 5 : set_name("MultiLoop_" + 75 [ + - ]: 15 : std::to_string(MultiLoopGlobal::get().instance_index.fetch_add(1, std::memory_order_relaxed))); 76 : : 77 : : // if VUNLIKELY (type == MultiLoop::kPriorityType) { 78 : : // CLOG_F("MultiLoop not support priority type(%s).", get_name().c_str()); 79 : : // } 80 : 5 : } 81 : : 82 : 24 : MultiLoop::~MultiLoop() = default; 83 : : 84 : 23 : bool MultiLoop::is_in_same_thread() const { 85 [ + - - + ]: 23 : if (MessageLoop::is_in_same_thread()) { 86 : : return true; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 87 : : } 88 : : 89 [ + - ]: 23 : std::lock_guard lock(impl_->pool_mtx); 90 : : 91 [ - + ]: 23 : if VUNLIKELY (!impl_->thread_pool) { 92 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 93 : : } 94 : : 95 [ + - ]: 23 : return impl_->thread_pool->is_in_work_thread(); 96 : 23 : } 97 : : 98 : 5 : bool MultiLoop::wait_for_idle(int ms, bool check) { 99 : 5 : const auto start_time = std::chrono::steady_clock::now(); 100 : : 101 : 16 : auto idle_predicate = [this]() -> bool { return impl_->pending_tasks.load(std::memory_order_acquire) == 0U; }; 102 : : 103 : 30 : auto remaining_ms = [ms, start_time]() -> int { 104 [ - + ]: 10 : if (ms == Timer::kInfinite) { 105 : : return Timer::kInfinite; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 106 : : } 107 : : 108 : : const auto elapsed = 109 [ + - + - ]: 10 : std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start_time).count(); 110 : : 111 [ - + ]: 10 : if (elapsed >= ms) { 112 : : return 0; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 113 : : } 114 : : 115 : 10 : return static_cast<int>(ms - elapsed); 116 : 5 : }; 117 : : 118 : 5 : bool first_check = check; 119 : : 120 : : while (true) { 121 [ + - ]: 5 : const int dispatcher_wait_ms = remaining_ms(); 122 : : 123 [ + - - + ]: 5 : if (!MessageLoop::wait_for_idle(dispatcher_wait_ms, first_check)) { 124 : 5 : return false; 125 : : } 126 : : 127 : 5 : first_check = false; 128 : : 129 [ + - ]: 5 : std::unique_lock lock(impl_->idle_mtx); 130 : : 131 [ - + ]: 5 : if (ms == Timer::kInfinite) { 132 : : impl_->idle_cv.wait(lock, idle_predicate); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 133 : : } else { 134 [ + - ]: 5 : const int worker_wait_ms = remaining_ms(); 135 : : 136 [ - + ]: 5 : if (worker_wait_ms <= 0) { 137 : : return idle_predicate() && MessageLoop::wait_for_idle(0, false); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 138 : : } 139 : : 140 [ - + ]: 5 : if (!impl_->idle_cv.wait_for(lock, std::chrono::milliseconds(worker_wait_ms), 141 : : idle_predicate)) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE 142 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 143 : : } 144 : : } 145 : : 146 [ + - ]: 5 : lock.unlock(); 147 : : 148 [ + - + - ]: 5 : if (MessageLoop::wait_for_idle(0, false)) { 149 : 5 : return true; 150 : : } 151 [ - + ]: 5 : } 152 : : } 153 : : 154 : 17 : void MultiLoop::on_begin() { 155 : : { 156 [ + - ]: 17 : std::lock_guard lock(impl_->pool_mtx); 157 : : 158 [ + - + + ]: 17 : if (get_type() == MultiLoop::kNormalType) { 159 [ + - ]: 16 : impl_->thread_pool.emplace(impl_->thread_num, ThreadPool::kNormalType); 160 [ + - - + ]: 1 : } else if (get_type() == MultiLoop::kLockfreeType) { 161 [ # # ]: 0 : impl_->thread_pool.emplace(impl_->thread_num, ThreadPool::kLockfreeType); 162 [ + - + - ]: 1 : } else if (get_type() == MultiLoop::kPriorityType) { 163 [ + - ]: 1 : impl_->thread_pool.emplace(impl_->thread_num, ThreadPool::kNormalType); 164 : : } 165 : 17 : } 166 : : 167 : 17 : MessageLoop::on_begin(); 168 : 17 : } 169 : : 170 : 16 : void MultiLoop::on_end() { 171 : : { 172 [ + - ]: 16 : std::lock_guard lock(impl_->pool_mtx); 173 : : 174 [ + - ]: 16 : if (impl_->thread_pool) { 175 [ + - ]: 16 : impl_->thread_pool->shutdown(); 176 : 16 : impl_->thread_pool.reset(); 177 : : } 178 : 16 : } 179 : : 180 : 16 : MessageLoop::on_end(); 181 : 16 : } 182 : : 183 : 144 : void MultiLoop::on_task_changed(Callback&& callback, uint32_t start_time) { 184 [ + - ]: 144 : auto task = std::make_shared<Callback>(std::move(callback)); 185 : : 186 : 144 : bool posted = false; 187 : : 188 : : { 189 [ + - ]: 144 : std::lock_guard lock(impl_->pool_mtx); 190 : : 191 [ + - ]: 144 : if VLIKELY (impl_->thread_pool) { 192 : 144 : impl_->pending_tasks.fetch_add(1U, std::memory_order_acq_rel); 193 : : 194 : 144 : std::shared_ptr<Impl> pending_token(impl_.get(), [](Impl* impl) noexcept { 195 : 144 : bool should_notify = false; 196 : : 197 : : { 198 : 144 : std::lock_guard<std::mutex> lock(impl->idle_mtx); 199 : 144 : should_notify = impl->pending_tasks.fetch_sub(1U, std::memory_order_acq_rel) == 1U; 200 : 144 : } 201 : : 202 [ + + ]: 144 : if (should_notify) { 203 : 69 : impl->idle_cv.notify_all(); 204 : : } 205 [ + - ]: 144 : }); 206 : : 207 [ + - + - ]: 144 : posted = impl_->thread_pool->post_task([this, task, pending_token = std::move(pending_token), 208 : : start_time]() mutable { // LCOV_EXCL_LINE GCOVR_EXCL_LINE 209 [ + - ]: 144 : if VLIKELY (*task) { 210 : 144 : MessageLoop::on_task_changed(std::move(*task), start_time); 211 : : } 212 : : 213 : : (void)pending_token; 214 : 144 : }); 215 : 144 : } 216 : 144 : } 217 : : 218 [ - + - - : 144 : if VUNLIKELY (!posted && *task) { - + ] 219 : : MessageLoop::on_task_changed(std::move(*task), start_time); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 220 : : } 221 : 144 : } 222 : : 223 : : } // namespace vlink