blob: 63ba4f7203a1faf8753a7a76b44ff6ff89cc90c9 [file] [log] [blame] [edit]
/*
* 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