blob: e45d9cb2473ee60dc11b6b6eba35f3003fbe0e30 [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 <phosphor-logging/lg2.hpp>
#include <coroutine>
namespace requester
{
/** @struct Coroutine
*
* A coroutine return_object supports nesting coroutine
*/
struct Coroutine
{
/** @brief The nested struct named 'promise_type' which is needed for
* Coroutine struct to be a coroutine return_object.
*/
struct promise_type
{
/** @brief For keeping the parent coroutine handle if any. For the case
* of nesting co_await coroutine, this handle will be used to resume to
* continue parent coroutine.
*/
std::coroutine_handle<> parent_handle;
/** @brief For holding return value of coroutine
*/
uint8_t data;
bool detached = false;
/** @brief Get the return object object
*/
Coroutine get_return_object()
{
return {std::coroutine_handle<promise_type>::from_promise(*this)};
}
/** @brief The method is called before starting a coroutine. Returning
* std::suspend_never awaitable to execute coroutine body immediately.
*/
std::suspend_never initial_suspend()
{
return {};
}
/** @brief The method is called after coroutine completed to return a
* customized awaitable object to resume to parent coroutine if any.
*/
auto final_suspend() noexcept
{
struct awaiter
{
/** @brief Returning false to make await_suspend to be called.
*/
bool await_ready() const noexcept
{
return false;
}
/** @brief Do nothing here for customized awaitable object.
*/
void await_resume() const noexcept {}
/** @brief Returning parent coroutine handle here to continue
* parent coroutine.
*/
std::coroutine_handle<> await_suspend(
std::coroutine_handle<promise_type> h) noexcept
{
auto parent_handle = h.promise().parent_handle;
if (h.promise().detached)
{
h.destroy();
}
if (parent_handle)
{
return parent_handle;
}
return std::noop_coroutine();
}
};
return awaiter{};
}
/** @brief The handler for an exception was thrown in
* coroutine body.
*/
void unhandled_exception()
{
try
{
throw; // Rethrow the current exception to handle it
}
catch (const std::exception& e)
{
lg2::error("Caught exception:: {HANDLER_EXCEPTION}",
"HANDLER_EXCEPTION", e.what());
}
}
/** @brief Keeping the value returned by co_return operator
*/
void return_value(uint8_t value) noexcept
{
data = std::move(value);
}
};
/** @brief Called by co_await to check if it needs to be
* suspened.
*/
bool await_ready() const noexcept
{
return handle.done();
}
/** @brief Called by co_await operator to get return value when coroutine
* finished.
*/
uint8_t await_resume() const noexcept
{
return std::move(handle.promise().data);
}
/** @brief Called when the coroutine itself is being suspended. The
* recording the parent coroutine handle is for await_suspend() in
* promise_type::final_suspend to refer.
*/
bool await_suspend(std::coroutine_handle<> coroutine)
{
handle.promise().parent_handle = coroutine;
return true;
}
~Coroutine()
{
if (handle && handle.done())
{
handle.destroy();
}
}
void detach()
{
if (!handle)
{
return;
}
if (handle.done())
{
handle.destroy();
}
else
{
handle.promise().detached = true;
}
handle = nullptr;
}
/** @brief Assigned by promise_type::get_return_object to keep coroutine
* handle itself.
*/
mutable std::coroutine_handle<promise_type> handle;
};
} // namespace requester