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 : : #include "./base/utils.h" 37 : : 38 : : namespace vlink { 39 : : 40 : : // MultiLoopGlobal 41 : : struct MultiLoopGlobal final { 42 : : std::atomic<int> instance_index{0}; 43 : : 44 : 36 : static MultiLoopGlobal& get() { 45 : : static MultiLoopGlobal instance; 46 : : 47 : 36 : return instance; 48 : : } 49 : : 50 : : private: 51 : : MultiLoopGlobal() = default; 52 : : }; 53 : : 54 : : // MultiLoop::Impl 55 : : struct MultiLoop::Impl final { 56 : : mutable std::mutex pool_mtx; 57 : : mutable std::mutex idle_mtx; 58 : : ConditionVariable idle_cv; 59 : : std::atomic_size_t pending_tasks{0U}; 60 : : std::optional<ThreadPool> thread_pool; 61 : : size_t thread_num{0}; 62 : : }; 63 : : 64 : : struct MultiLoop::PendingTask final { 65 : : MultiLoop* loop; 66 : : MultiLoop::Impl* impl; 67 : : MultiLoop::Callback callback; 68 : : uint32_t start_time; 69 : : 70 : 70162 : PendingTask(MultiLoop* target_loop, MultiLoop::Impl* target_impl, MultiLoop::Callback&& target_callback, 71 : : uint32_t target_start_time) noexcept 72 : 70162 : : loop(target_loop), impl(target_impl), callback(std::move(target_callback)), start_time(target_start_time) { 73 : 70162 : impl->pending_tasks.fetch_add(1U, std::memory_order_acq_rel); 74 : 70162 : } 75 : : 76 : : PendingTask(const PendingTask&) = delete; 77 : : 78 : : PendingTask& operator=(const PendingTask&) = delete; 79 : : 80 : 70234 : ~PendingTask() { 81 [ + + ]: 140324 : if VLIKELY (impl->pending_tasks.fetch_sub(1U, std::memory_order_acq_rel) != 1U) { 82 : 70090 : return; 83 : : } 84 : : 85 : : { 86 : 72 : std::lock_guard<std::mutex> lock(impl->idle_mtx); 87 : 72 : } 88 : : 89 : 72 : impl->idle_cv.notify_all(); 90 [ + + ]: 70162 : } 91 : : }; 92 : : 93 : : // MultiLoop 94 [ + - ]: 24 : MultiLoop::MultiLoop(size_t thread_num) : impl_(std::make_unique<Impl>()) { 95 : 24 : impl_->thread_num = thread_num; 96 : : 97 [ + - + - ]: 24 : set_name("MultiLoop_" + 98 [ + - ]: 72 : std::to_string(MultiLoopGlobal::get().instance_index.fetch_add(1, std::memory_order_relaxed))); 99 : 24 : } 100 : : 101 [ + - ]: 12 : MultiLoop::MultiLoop(size_t thread_num, Type type) : MessageLoop(type), impl_(std::make_unique<Impl>()) { 102 : 12 : impl_->thread_num = thread_num; 103 : : 104 [ + - + - ]: 12 : set_name("MultiLoop_" + 105 [ + - ]: 36 : std::to_string(MultiLoopGlobal::get().instance_index.fetch_add(1, std::memory_order_relaxed))); 106 : : 107 : : // if VUNLIKELY (type == MultiLoop::kPriorityType) { 108 : : // CLOG_F("MultiLoop not support priority type(%s).", get_name().c_str()); 109 : : // } 110 : 12 : } 111 : : 112 : 36 : MultiLoop::~MultiLoop() { 113 : : #ifdef _WIN32 114 : : if VUNLIKELY (Utils::is_terminating()) { 115 : : return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 116 : : } 117 : : #endif 118 : : 119 : 36 : quit(!is_running()); 120 : : 121 [ + - ]: 36 : if VLIKELY (!MultiLoop::is_in_same_thread()) { 122 : 36 : wait_for_quit(Timer::kInfinite, false); 123 : : } 124 : 36 : } 125 : : 126 : 80 : bool MultiLoop::is_in_same_thread() const { 127 [ + - - + ]: 80 : if (MessageLoop::is_in_same_thread()) { 128 : : return true; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 129 : : } 130 : : 131 [ + - ]: 80 : std::lock_guard lock(impl_->pool_mtx); 132 : : 133 [ + + ]: 80 : if VUNLIKELY (!impl_->thread_pool) { 134 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 135 : : } 136 : : 137 [ + - ]: 46 : return impl_->thread_pool->is_in_work_thread(); 138 : 80 : } 139 : : 140 : 13 : bool MultiLoop::wait_for_idle(int ms, bool check) { 141 : 13 : const auto start_time = std::chrono::steady_clock::now(); 142 : : 143 : 48 : auto idle_predicate = [this]() -> bool { return impl_->pending_tasks.load(std::memory_order_acquire) == 0U; }; 144 : : 145 : 78 : auto remaining_ms = [ms, start_time]() -> int { 146 [ - + ]: 26 : if (ms == Timer::kInfinite) { 147 : : return Timer::kInfinite; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 148 : : } 149 : : 150 : : const auto elapsed = 151 [ + - + - ]: 26 : std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start_time).count(); 152 : : 153 [ - + ]: 26 : if (elapsed >= ms) { 154 : : return 0; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 155 : : } 156 : : 157 : 26 : return static_cast<int>(ms - elapsed); 158 : 13 : }; 159 : : 160 : 13 : bool first_check = check; 161 : : 162 : : while (true) { 163 [ + - ]: 13 : const int dispatcher_wait_ms = remaining_ms(); 164 : : 165 [ + - - + ]: 13 : if (!MessageLoop::wait_for_idle(dispatcher_wait_ms, first_check)) { 166 : 13 : return false; 167 : : } 168 : : 169 : 13 : first_check = false; 170 : : 171 [ + - ]: 13 : std::unique_lock lock(impl_->idle_mtx); 172 : : 173 [ - + ]: 13 : if (ms == Timer::kInfinite) { 174 : : impl_->idle_cv.wait(lock, idle_predicate); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 175 : : } else { 176 [ + - ]: 13 : const int worker_wait_ms = remaining_ms(); 177 : : 178 [ - + ]: 13 : if (worker_wait_ms <= 0) { 179 : : return idle_predicate() && MessageLoop::wait_for_idle(0, false); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 180 : : } 181 : : 182 [ + - - + ]: 13 : if (!impl_->idle_cv.wait_for(lock, std::chrono::milliseconds(worker_wait_ms), 183 : : idle_predicate)) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE 184 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 185 : : } 186 : : } 187 : : 188 [ + - ]: 13 : lock.unlock(); 189 : : 190 [ + - + - ]: 13 : if (MessageLoop::wait_for_idle(0, false)) { 191 : 13 : return true; 192 : : } 193 [ - + ]: 13 : } 194 : : } 195 : : 196 : 29 : void MultiLoop::on_begin() { 197 : : { 198 [ + - ]: 29 : std::lock_guard lock(impl_->pool_mtx); 199 : : 200 [ + - + + ]: 29 : if (get_type() == MultiLoop::kNormalType) { 201 [ + - ]: 26 : impl_->thread_pool.emplace(impl_->thread_num, ThreadPool::kNormalType); 202 [ + - - + ]: 3 : } else if (get_type() == MultiLoop::kLockfreeType) { 203 [ # # ]: 0 : impl_->thread_pool.emplace(impl_->thread_num, ThreadPool::kLockfreeType); 204 [ + - + - ]: 3 : } else if (get_type() == MultiLoop::kPriorityType) { 205 [ + - ]: 3 : impl_->thread_pool.emplace(impl_->thread_num, ThreadPool::kNormalType); 206 : : } 207 : 29 : } 208 : : 209 : 29 : MessageLoop::on_begin(); 210 : 29 : } 211 : : 212 : 29 : void MultiLoop::on_end() { 213 : 29 : ThreadPool* thread_pool = nullptr; 214 : : 215 : : { 216 [ + - ]: 29 : std::lock_guard lock(impl_->pool_mtx); 217 : : 218 [ + - ]: 29 : if (impl_->thread_pool) { 219 : 29 : thread_pool = &*impl_->thread_pool; 220 : : } 221 : 29 : } 222 : : 223 [ + - ]: 29 : if (thread_pool != nullptr) { 224 : 29 : thread_pool->shutdown(); 225 : : } 226 : : 227 : : { 228 [ + - ]: 29 : std::lock_guard lock(impl_->pool_mtx); 229 : 29 : impl_->thread_pool.reset(); 230 : 29 : } 231 : : 232 : 29 : MessageLoop::on_end(); 233 : 29 : } 234 : : 235 : 70162 : void MultiLoop::on_task_changed(Callback&& callback, uint32_t start_time) { 236 : 70162 : std::shared_ptr<PendingTask> task; 237 : 70162 : Callback dropped_task; 238 : 70162 : bool posted = false; 239 : : 240 : : { 241 [ + - ]: 70162 : std::lock_guard lock(impl_->pool_mtx); 242 : : 243 [ + - ]: 70162 : if VLIKELY (impl_->thread_pool) { 244 [ + - ]: 70162 : task = std::make_shared<PendingTask>(this, impl_.get(), std::move(callback), start_time); 245 : : 246 [ + - + - ]: 140324 : posted = impl_->thread_pool->push_task( 247 : : [task]() mutable { // LCOV_EXCL_LINE GCOVR_EXCL_LINE 248 [ + - ]: 70153 : if VLIKELY (task->callback) { 249 : 70153 : task->loop->MessageLoop::on_task_changed(std::move(task->callback), task->start_time); 250 : : } 251 : 70152 : }, 252 [ + - ]: 70162 : is_current_task_droppable(), TaskOverflowPolicy::kUseDispatcherStrategy, nullptr, &dropped_task); 253 : : } 254 : 70162 : } 255 : : 256 [ + + ]: 70162 : if VUNLIKELY (!posted) { 257 [ + - ]: 3 : if (task) { 258 [ + - ]: 3 : if (task->callback) { 259 : : MessageLoop::on_task_changed(std::move(task->callback), task->start_time); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 260 : : } 261 [ # # ]: 0 : } else if (callback) { 262 : : MessageLoop::on_task_changed(std::move(callback), start_time); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 263 : : } 264 : : } 265 : 70162 : } 266 : : 267 : : } // namespace vlink