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/schedule.h" 25 : : 26 : : #include <chrono> 27 : : #include <cstdint> 28 : : #include <memory> 29 : : #include <mutex> 30 : : #include <optional> 31 : : #include <utility> 32 : : #include <vector> 33 : : 34 : : #include "./base/logger.h" 35 : : #include "./base/memory_resource.h" 36 : : 37 : : namespace vlink { 38 : : 39 : : // Schedule::Config 40 : 25 : Schedule::Config::Config() = default; 41 : : 42 : 18 : Schedule::Config::Config(uint32_t _delay_ms, uint16_t _priority, uint32_t _schedule_timeout_ms, 43 : 18 : uint32_t _execution_timeout_ms) 44 : 18 : : delay_ms(_delay_ms), 45 : 18 : priority(_priority), 46 : 18 : schedule_timeout_ms(_schedule_timeout_ms), 47 : 18 : execution_timeout_ms(_execution_timeout_ms) {} 48 : : 49 : : // Schedule::Status 50 : 44 : Schedule::Status::Status() : impl_(MemoryResource::make_shared<Schedule::Status::Impl>()) { 51 : 44 : impl_->is_valid.store(true, std::memory_order_relaxed); 52 : 44 : } 53 : : 54 : 73 : Schedule::Status::~Status() { commit(); } 55 : : 56 : 29 : Schedule::Status::Status(Status&& status) noexcept { 57 [ - + ]: 29 : if VUNLIKELY (this == &status) { 58 : : return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 59 : : } 60 : : 61 : 29 : impl_ = std::move(status.impl_); 62 : 29 : launcher_ = std::move(status.launcher_); 63 : 29 : committed_ = status.committed_; 64 : : } 65 : : 66 : 2 : Schedule::Status& Schedule::Status::operator=(Status&& status) noexcept { 67 [ - + ]: 2 : if VUNLIKELY (this == &status) { 68 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 69 : : } 70 : : 71 : 2 : commit(); 72 : : 73 : 2 : impl_ = std::move(status.impl_); 74 : 2 : launcher_ = std::move(status.launcher_); 75 : 2 : committed_ = status.committed_; 76 : : 77 : 2 : return *this; 78 : : } 79 : : 80 : 83 : void Schedule::Status::commit() noexcept { 81 [ + + + + : 83 : if (!impl_ || committed_) { + + ] 82 : 39 : return; 83 : : } 84 : : 85 : 44 : committed_ = true; 86 : : 87 [ + + ]: 44 : if VLIKELY (launcher_) { 88 : 28 : launcher_(); 89 : : } 90 : : } 91 : : 92 : 4 : void Schedule::Status::set_valid(bool valid) { 93 [ + - ]: 4 : if VLIKELY (impl_) { 94 : 4 : impl_->is_valid.store(valid, std::memory_order_relaxed); 95 : : } 96 : 4 : } 97 : : 98 [ + - + + ]: 46 : bool Schedule::Status::is_valid() const { return impl_ && impl_->is_valid.load(std::memory_order_relaxed); } 99 : : 100 : 8 : bool Schedule::Status::dispatch() { 101 : 8 : commit(); 102 : : 103 : 8 : return is_valid(); 104 : : } 105 : : 106 : 3 : Schedule::Status& Schedule::Status::on_execution_timeout(Callback&& callback) { 107 [ + - ]: 3 : if VLIKELY (is_valid()) { 108 [ + - ]: 3 : std::lock_guard lock(impl_->mtx); 109 : : 110 [ - + ]: 3 : if VUNLIKELY (impl_->dispatched.load(std::memory_order_acquire)) { 111 : : VLOG_E("Schedule: on_execution_timeout registered after dispatch; ignored."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 112 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 113 : : } 114 : : 115 [ - + ]: 3 : if VUNLIKELY (impl_->execution_timeout_callback) { 116 : : VLOG_E("Schedule: Execution timeout callback is already set."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 117 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 118 : : } 119 : : 120 : 3 : impl_->execution_timeout_callback = std::move(callback); 121 [ + - ]: 3 : } 122 : : 123 : 3 : return *this; 124 : : } 125 : : 126 : 3 : Schedule::Status& Schedule::Status::on_schedule_timeout(Callback&& callback) { 127 [ + - ]: 3 : if VLIKELY (is_valid()) { 128 [ + - ]: 3 : std::lock_guard lock(impl_->mtx); 129 : : 130 [ - + ]: 3 : if VUNLIKELY (impl_->dispatched.load(std::memory_order_acquire)) { 131 : : VLOG_E("Schedule: on_schedule_timeout registered after dispatch; ignored."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 132 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 133 : : } 134 : : 135 [ - + ]: 3 : if VUNLIKELY (impl_->schedule_timeout_callback) { 136 : : VLOG_E("Schedule: Schedule timeout callback is already set."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 137 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 138 : : } 139 : : 140 : 3 : impl_->schedule_timeout_callback = std::move(callback); 141 [ + - ]: 3 : } 142 : : 143 : 3 : return *this; 144 : : } 145 : : 146 : 5 : Schedule::Status& Schedule::Status::on_catch(CatchCallback&& callback) { 147 [ + - ]: 5 : if VLIKELY (is_valid()) { 148 [ + - ]: 5 : std::lock_guard lock(impl_->mtx); 149 : : 150 [ - + ]: 5 : if VUNLIKELY (impl_->dispatched.load(std::memory_order_acquire)) { 151 : : VLOG_E("Schedule: on_catch registered after dispatch; ignored."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 152 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 153 : : } 154 : : 155 [ - + ]: 5 : if VUNLIKELY (impl_->catch_callback) { 156 : : VLOG_E("Schedule: Catch callback is already set."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 157 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 158 : : } 159 : : 160 : 5 : impl_->catch_callback = std::move(callback); 161 [ + - ]: 5 : } 162 : : 163 : 5 : return *this; 164 : : } 165 : : 166 : : // Schedule::RetStatus 167 : 6 : Schedule::Status& Schedule::RetStatus::on_else(Callback&& callback) { 168 [ + - ]: 6 : if VLIKELY (is_valid()) { 169 [ + - ]: 6 : std::lock_guard lock(impl_->mtx); 170 : : 171 [ - + ]: 6 : if VUNLIKELY (impl_->dispatched.load(std::memory_order_acquire)) { 172 : : VLOG_E("Schedule: on_else registered after dispatch; ignored."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 173 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 174 : : } 175 : : 176 [ - + ]: 6 : if VUNLIKELY (impl_->else_callback) { 177 : : VLOG_E("Schedule: Else callback is already set."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 178 : : return *this; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 179 : : } 180 : : 181 : 6 : impl_->else_callback = std::move(callback); 182 [ + - ]: 6 : } 183 : : 184 : 6 : return *this; 185 : : } 186 : : 187 : 15 : Schedule::RetStatus& Schedule::RetStatus::on_then(RetCallback&& callback) { 188 [ + - ]: 15 : if VLIKELY (is_valid()) { 189 [ + - ]: 15 : std::lock_guard lock(impl_->mtx); 190 : : 191 [ + + ]: 15 : if VUNLIKELY (impl_->dispatched.load(std::memory_order_acquire)) { 192 [ + - + - ]: 2 : VLOG_E("Schedule: on_then registered after dispatch; ignored."); 193 : 1 : return *this; 194 : : } 195 : : 196 [ + - ]: 14 : impl_->then_callback_list.emplace_back(std::move(callback)); 197 [ + + ]: 15 : } 198 : : 199 : 14 : return *this; 200 : : } 201 : : 202 : : // Schedule 203 : 28 : Schedule::Status Schedule::process(const Config& config, Callback&& callback, Callback& wrapper_callback) { 204 : 28 : RetCallback adapted_callback = [callback = std::move(callback)]() mutable -> bool { 205 [ + - ]: 24 : if VLIKELY (callback) { 206 : 24 : callback(); 207 : : } 208 : : 209 : 23 : return true; 210 [ + - ]: 28 : }; 211 : : 212 [ + - ]: 56 : return internal_process_with_ret(config, std::move(adapted_callback), wrapper_callback); 213 : 28 : } 214 : : 215 : 11 : Schedule::RetStatus Schedule::process_with_ret(const Config& config, RetCallback&& callback, 216 : : Callback& wrapper_callback) { 217 : 11 : return internal_process_with_ret(config, std::move(callback), wrapper_callback); 218 : : } 219 : : 220 : 39 : Schedule::RetStatus Schedule::internal_process_with_ret(const Config& config, RetCallback&& callback, 221 : : Callback& wrapper_callback) { 222 [ + - ]: 39 : Schedule::RetStatus status; 223 : : 224 : 39 : auto submit_time = std::chrono::steady_clock::now(); 225 : : 226 : 39 : wrapper_callback = [callback = std::move(callback), config, submit_time, impl = status.impl_]() mutable { 227 : 35 : Schedule::Callback schedule_timeout_cb; 228 : 35 : Schedule::Callback execution_timeout_cb; 229 : 35 : Schedule::CatchCallback catch_cb; 230 : 35 : Schedule::Callback else_cb; 231 : 35 : std::vector<Schedule::RetCallback> then_callbacks; 232 : : 233 : : { 234 [ + - ]: 35 : std::lock_guard lock(impl->mtx); 235 : : 236 [ - + ]: 35 : if VUNLIKELY (!impl->is_valid.load(std::memory_order_relaxed)) { 237 : : return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 238 : : } 239 : : 240 : 35 : schedule_timeout_cb = std::move(impl->schedule_timeout_callback); 241 : 35 : execution_timeout_cb = std::move(impl->execution_timeout_callback); 242 : 35 : catch_cb = std::move(impl->catch_callback); 243 : 35 : else_cb = std::move(impl->else_callback); 244 : 35 : then_callbacks = std::move(impl->then_callback_list); 245 : : 246 : 35 : impl->dispatched.store(true, std::memory_order_release); 247 [ + - ]: 35 : } 248 : : 249 : 35 : auto now = std::chrono::steady_clock::now(); 250 : : 251 [ + + ]: 35 : if (config.schedule_timeout_ms > 0) { 252 [ + - + - ]: 2 : auto wait_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - submit_time).count(); 253 : : 254 [ + + ]: 2 : if (static_cast<uint32_t>(wait_ms) > config.schedule_timeout_ms + config.delay_ms) { 255 [ + - ]: 1 : if (schedule_timeout_cb) { 256 [ + - ]: 1 : schedule_timeout_cb(); 257 : : } 258 : : 259 : 1 : return; 260 : : } 261 : : } 262 : : 263 : 76 : auto run_with_timeout = [&catch_cb, &config, 264 : 49 : &execution_timeout_cb](Schedule::RetCallback& exe_callback) -> std::optional<bool> { 265 [ - + ]: 42 : if VUNLIKELY (!exe_callback) { 266 : : return std::nullopt; // LCOV_EXCL_LINE GCOVR_EXCL_LINE 267 : : } 268 : : 269 : 42 : auto exec_start = std::chrono::steady_clock::now(); 270 : : 271 : 42 : bool result = false; 272 : : 273 : : try { 274 [ + + ]: 42 : result = exe_callback(); 275 [ - + ]: 3 : } catch (std::exception& e) { 276 [ + - ]: 3 : if (catch_cb) { 277 [ + - ]: 3 : catch_cb(e); 278 : : 279 : 3 : return std::nullopt; 280 : : } 281 [ + - ]: 3 : } 282 : : 283 : 39 : auto exec_end = std::chrono::steady_clock::now(); 284 : : 285 [ + + ]: 39 : if (config.execution_timeout_ms > 0) { 286 [ + - + - ]: 2 : auto exec_ms = std::chrono::duration_cast<std::chrono::milliseconds>(exec_end - exec_start).count(); 287 : : 288 [ + + ]: 2 : if (static_cast<uint32_t>(exec_ms) > config.execution_timeout_ms) { 289 [ + - ]: 1 : if (execution_timeout_cb) { 290 [ + - ]: 1 : execution_timeout_cb(); 291 : : } 292 : : 293 : 1 : return std::nullopt; 294 : : } 295 : : } 296 : : 297 : 38 : return result; 298 : 34 : }; 299 : : 300 [ + - ]: 34 : auto main_ret = run_with_timeout(callback); 301 : : 302 [ + + ]: 34 : if (!main_ret.has_value()) { 303 : 4 : return; 304 : : } 305 : : 306 [ + - + + ]: 30 : if (!main_ret.value()) { 307 [ + - ]: 2 : if (else_cb) { 308 [ + - ]: 2 : else_cb(); 309 : : } 310 : : 311 : 2 : return; 312 : : } 313 : : 314 [ + + ]: 35 : for (auto& then_callback : then_callbacks) { 315 [ + - ]: 8 : auto then_ret = run_with_timeout(then_callback); 316 : : 317 [ - + ]: 8 : if (!then_ret.has_value()) { 318 : 1 : return; 319 : : } 320 : : 321 [ + - + + ]: 8 : if (!then_ret.value()) { 322 [ - + ]: 1 : if (else_cb) { 323 [ # # ]: 0 : else_cb(); 324 : : } 325 : : 326 : 1 : return; 327 : : } 328 : : } 329 [ + - + + : 106 : }; + + + + + + + + ] 330 : : 331 : 78 : return status; 332 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE 333 : : 334 : : } // namespace vlink