| /* |
| * SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & |
| * AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include "libnsm/base.h" |
| |
| #include <sdeventplus/event.hpp> |
| |
| #include <coroutine> |
| |
| namespace common |
| { |
| class TimerAwaiter |
| { |
| public: |
| /** @brief Called by co_await to check if it needs to be |
| * suspened. |
| */ |
| bool await_ready() const noexcept |
| { |
| return false; |
| } |
| |
| /** @brief Called by co_await operator to get return void when coroutine |
| * finished. |
| */ |
| nsm_sw_codes await_resume() const noexcept |
| { |
| return rc; |
| } |
| |
| /** @brief Called to suspend the coroutine and register the timer in the |
| * event loop. */ |
| bool await_suspend(std::coroutine_handle<> handle) noexcept |
| { |
| this->handle = handle; // Store the handle to resume later |
| return start(); |
| } |
| |
| TimerAwaiter() = default; |
| TimerAwaiter& operator=(const TimerAwaiter& other) = delete; |
| |
| /** @brief Constructor to initialize the awaitable object. |
| * |
| * @param time Duration of the timer in milliseconds. |
| */ |
| TimerAwaiter(uint64_t time) : |
| durationInUsec(time * 1000), // Convert mSec to uSec |
| rc(NSM_SW_SUCCESS) // Initialize the result code to success |
| {} |
| ~TimerAwaiter() |
| { |
| if (handle && handle.done()) |
| { |
| handle.destroy(); |
| } |
| } |
| |
| /** @brief Starts the timer. */ |
| bool start() |
| { |
| isRunning = isExpired = false; |
| uint64_t now; |
| if (sd_event_now(event.get(), CLOCK_MONOTONIC, &now) < 0) |
| { |
| rc = NSM_SW_ERROR; // Failure scenario handling |
| return false; // Don't suspend on failure |
| } |
| |
| if (sd_event_add_time( |
| event.get(), &eventSource, CLOCK_MONOTONIC, |
| now + durationInUsec, // Timer expiration in microseconds |
| 0, // Accuracy |
| &TimerAwaiter::resume, // Callback function |
| this) < 0) |
| { |
| rc = NSM_SW_ERROR; // Handle errors from adding the timer |
| return false; // Don't suspend on failure |
| } |
| |
| // sd_event_source_set_priority(eventSource, SD_EVENT_PRIORITY_NORMAL); |
| startTime = now; |
| isRunning = true; |
| return true; // Coroutine suspended successfully |
| } |
| |
| /** @brief Stops the timer to resume the coroutine. */ |
| bool stop() |
| { |
| if (isRunning) |
| { |
| sd_event_source_unref(eventSource); // Cancel the timer event source |
| if (sd_event_add_time(event.get(), &eventSource, CLOCK_MONOTONIC, |
| 0, // Call the callback immediately |
| 0, |
| &TimerAwaiter::resume, // Callback function |
| this) < 0) |
| { |
| rc = NSM_SW_ERROR; // Handle errors from adding the timer |
| return false; |
| } |
| // sd_event_source_set_priority(eventSource, |
| // SD_EVENT_PRIORITY_NORMAL); |
| } |
| return true; |
| } |
| |
| /** @brief Checks if the timer expired. */ |
| bool expired() const |
| { |
| return isExpired; |
| } |
| |
| /** @brief Checks if the timer is running. */ |
| bool running() const |
| { |
| return isRunning; |
| } |
| |
| private: |
| /** @brief Handle to resume the suspended coroutine. */ |
| std::coroutine_handle<> handle; |
| |
| /** @brief Flag to indicate if the timer is running. */ |
| bool isRunning = false; |
| /** @brief Flag to indicate if the timer is expired. */ |
| bool isExpired = false; |
| |
| /** @brief Pointer to the event source created by `sd_event_add_time`. */ |
| sd_event_source* eventSource = nullptr; |
| |
| /** @brief Reference to the event loop where the timer is registered. */ |
| const sdeventplus::Event event = sdeventplus::Event::get_default(); |
| |
| /** @brief Start time of the timer. */ |
| uint64_t startTime = 0; |
| |
| /** @brief Duration for which the coroutine should call the timer callback, |
| * in microseconds. |
| */ |
| uint64_t durationInUsec; |
| |
| /** @brief Result code, which can be used as a return value after |
| * resumption. */ |
| nsm_sw_codes rc; |
| |
| /** @brief Resumes the coroutine and sets the result code. |
| */ |
| void resume(uint64_t now) |
| { |
| if (isRunning) |
| { |
| isRunning = false; |
| isExpired = now >= (startTime + durationInUsec); |
| rc = isExpired ? NSM_SW_ERROR_TIMEOUT : NSM_SW_SUCCESS; |
| sd_event_source_unref(eventSource); |
| handle.resume(); // Resume the coroutine |
| } |
| } |
| |
| /** @brief Timer callback that resumes the coroutine. |
| * This is called by the event loop when the timeout occurs. |
| */ |
| static int resume(sd_event_source*, uint64_t now, void* userdata) |
| { |
| auto timer = static_cast<TimerAwaiter*>(userdata); |
| timer->resume(now); |
| return 0; |
| } |
| }; |
| } // namespace common |