diff --git a/modules/async_context.cppm b/modules/async_context.cppm index 286dee9..e1d99e3 100644 --- a/modules/async_context.cppm +++ b/modules/async_context.cppm @@ -37,14 +37,70 @@ export module async_context; namespace async::inline v0 { +/** + * @brief Unsigned 8-bit integer type alias + * + * This is a type alias for std::uint8_t, used throughout the async_context + * library for byte-sized operations. + */ export using u8 = std::uint8_t; + +/** + * @brief Byte type alias + * + * This is a type alias for std::uint8_t, used to represent byte-sized values. + * It's an alias for u8 and is provided for semantic clarity in contexts where + * byte-level operations are performed. + */ export using byte = std::uint8_t; + +/** + * @brief Size type alias + * + * This is a type alias for std::size_t, used to represent sizes and counts + * throughout the async_context library. + */ export using usize = std::size_t; + +/** + * @brief Unsigned pointer type alias + * + * This is a type alias for std::uintptr_t, used to represent pointer-sized + * unsigned integer values. + */ export using uptr = std::uintptr_t; +/** + * @brief Bit mask for pointer alignment checking + * + * This constant represents a bitmask used to check if a pointer is properly + * aligned to the word size (uptr). It's calculated as sizeof(uptr) - 1. + */ constexpr size_t mask = sizeof(uptr) - 1uz; + +/** + * @brief Bit shift for word alignment calculations + * + * This constant represents the number of bits to shift when calculating + * word-aligned memory requirements. It's calculated as + * std::countr_zero(sizeof(uptr)). + */ constexpr size_t shift = std::countr_zero(sizeof(uptr)); +/** + * @brief Enumeration of blocking states for async operations + * + * This enum describes the various states a coroutine can be in when blocked + * from execution. Each state has different implications for how the scheduler + * should handle resumption of the coroutine. + * + * The blocking states are ordered from least to most restrictive: + * - nothing: Ready to run, no blocking + * - time: Must wait for a specific duration before resuming + * - io: Blocked by I/O operation that must complete + * - sync: Blocked by resource contention (mutex, semaphore) + * - external: Blocked by external coroutine system + */ export enum class blocked_by : u8 { /// Not blocked by anything, ready to run, can be resumed. nothing = 0, @@ -111,22 +167,51 @@ export enum class blocked_by : u8 { external = 4, }; +/** + * @brief Represents an async execution context for coroutines + * + * The context class manages coroutine execution, memory allocation, and + * blocking states. It provides the infrastructure for running coroutines in + * stack-based environments without heap allocation. + * + * Derived classes must: + * 1. Provide stack memory via initialize_stack_memory() + * 2. Implement do_schedule() to handle blocking state notifications + * + * The context is designed to be cache-line optimized (≤ 64 bytes) and supports + * stack-based coroutine allocation. This makes it suitable for embedded systems + * with limited memory resources. + */ export class context; /** * @brief Thrown when an async::context runs out of stack memory * - * This occurs if a coroutine co_awaits a function and the coroutine promise - * cannot fit withint he context. + * This exception is thrown when a coroutine attempts to allocate more memory + * than is available in the context's stack buffer. It indicates that the + * context has insufficient memory to accommodate the coroutine's execution + * frame. * + * @note The violator pointer may not be valid when caught, as the context might + * have been destroyed during exception propagation. */ export struct bad_coroutine_alloc : std::bad_alloc { + /** + * @brief Construct a bad_coroutine_alloc exception + * + * @param p_violator Pointer to the context that ran out of memory + */ bad_coroutine_alloc(context const* p_violator) : violator(p_violator) { } + /** + * @brief Get exception message + * + * @return C-string describing the error condition + */ [[nodiscard]] char const* what() const noexcept override { return "An async::context ran out of memory!"; @@ -149,9 +234,17 @@ export struct bad_coroutine_alloc : std::bad_alloc /** * @brief Thrown when a coroutine awaits a cancelled future * + * This exception is thrown when a coroutine attempts to await a future that + * has been cancelled. It indicates that the operation was explicitly cancelled + * before completion. */ export class operation_cancelled : public std::exception { + /** + * @brief Get exception message + * + * @return C-string describing the cancellation error + */ [[nodiscard]] char const* what() const noexcept override { return "This future has been cancelled!"; @@ -179,6 +272,22 @@ export using block_info = class promise_base; +/** + * @brief The base context class for managing coroutine execution + * + * The context class is the core of the async_context library. It manages: + * + * - Stack-based coroutine allocation + * - Coroutine execution state and blocking information + * - Memory management for coroutine frames + * - Scheduler integration through virtual methods + * + * Derived classes must implement the do_schedule() method to integrate with + * custom schedulers and provide stack memory via initialize_stack_memory(). + * + * @note Context objects should be kept alive as long as coroutines are running + * on them. The context must be properly cleaned up to prevent memory leaks. + */ export class context { public: @@ -189,20 +298,56 @@ public: inline static auto const noop_sentinel = std::noop_coroutine(); static auto constexpr default_timeout = sleep_duration(0); + /** + * @brief Default constructor for context + * + * Creates an uninitialized context. Derived classes must call + * initialize_stack_memory() to properly set up the stack memory. + */ context() = default; + + /** + * @brief Delete copy constructor + * + * Contexts cannot be copied as they manage unique stack memory. + */ context(context const&) = delete; + + /** + * @brief Delete copy assignment operator + * + * Contexts cannot be copied as they manage unique stack memory. + */ context& operator=(context const&) = delete; + + /** + * @brief Delete move constructor + * + * Contexts cannot be moved as they manage unique stack memory. + */ context(context&&) = delete; + + /** + * @brief Delete move assignment operator + * + * Contexts cannot be moved as they manage unique stack memory. + */ context& operator=(context&&) = delete; /** - * @brief Implementations of context must call this API in their constructor - * in order to initialize the stack memory of this context. + * @brief Initialize stack memory for the context + * + * This method must be called by derived context implementations to provide + * the stack memory that will be used for coroutine frame allocation. * - * @param p_stack_memory - stack memory provided by the derived context. It is - * the responsibility of the derived context to manager this memory. If this + * @param p_stack_memory - Stack memory provided by the derived context. It is + * the responsibility of the derived context to manage this memory. If this * memory was dynamically allocated, then it is the responsibility of the * derived class to deallocate that memory. + * + * @note The stack memory must be properly aligned and sized to accommodate + * coroutine frames. This is a required step for any derived context + * implementation. */ constexpr void initialize_stack_memory(std::span p_stack_memory) { @@ -210,16 +355,46 @@ public: m_stack_pointer = m_stack.data(); } + /** + * @brief Unblocks a coroutine that was previously blocked + * + * This method transitions the context from any blocking state to "nothing" + * (ready to run) state. It's typically called by I/O completion handlers or + * when external conditions that were blocking a coroutine have been resolved. + * + * @note This method is noexcept and should not throw exceptions. + * It's used internally by I/O completion handlers to signal that a blocked + * coroutine can now proceed. + */ constexpr void unblock() noexcept { transition_to(blocked_by::nothing); } + /** + * @brief Unblocks a coroutine without notifying the scheduler + * + * This method transitions the context to "nothing" (ready to run) state but + * does not call the scheduler's do_schedule method. This is useful in cases + * where the scheduler state is being managed externally or during cleanup. + * + * @note This method is noexcept and should not throw exceptions. + */ constexpr void unblock_without_notification() noexcept { m_state = blocked_by::nothing; } + /** + * @brief Blocks the context for a specified time duration + * + * This method blocks the current coroutine for the specified duration, + * transitioning it to the time-blocking state. The scheduler is responsible + * for resuming this context after the duration has elapsed. + * + * @param p_duration The time duration to block for + * @return std::suspend_always to suspend the coroutine until resumed + */ constexpr std::suspend_always block_by_time( sleep_duration p_duration) noexcept { @@ -227,6 +402,16 @@ public: return {}; } + /** + * @brief Blocks the context for an I/O operation + * + * This method blocks the current coroutine until an I/O operation completes. + * The context can be resumed by calling unblock() when the I/O is ready. + * + * @param p_duration Optional time estimate for when to poll or reschedule + * the context again. The scheduler may ignore this hint. + * @return std::suspend_always to suspend the coroutine until resumed + */ constexpr std::suspend_always block_by_io( sleep_duration p_duration = default_timeout) noexcept { @@ -234,40 +419,100 @@ public: return {}; } + /** + * @brief Blocks the context by resource contention (sync) + * + * This method blocks the current coroutine until a synchronization resource + * becomes available. The context can be resumed at any time to check if the + * resource can be claimed. A scheduler can collect the set of contexts + * blocked by `p_blocker` and when `p_blocker` is no longer blocked by + * anything, unblock and resume any of those context to have them acquire + * access over the bus. + * + * @param p_blocker Pointer to the context that is currently blocking this one + * @return std::suspend_always to suspend the coroutine until resumed + */ constexpr std::suspend_always block_by_sync(context* p_blocker) noexcept { transition_to(blocked_by::sync, p_blocker); return {}; } + /** + * @brief Blocks the context by an external coroutine system + * + * This method blocks the current coroutine when it's awaiting an operation + * from an external coroutine library (e.g., std::task, std::generator). The + * coroutine is considered a supervising coroutine. The coroutine may be + * resumed while blocked by external. + * + * @return std::suspend_always to suspend the coroutine until resumed + */ constexpr std::suspend_always block_by_external() noexcept { transition_to(blocked_by::external, std::monostate{}); return {}; } + /** + * @brief Get the current active coroutine handle + * + * This method returns the coroutine handle that is currently active on this + * context. + * + * NOTE: It is UB to `destroy()` the returned handle. This coroutine is + * managed/owned by this context and thus, the returned coroutine MUST NOT be + * destroyed. + * + * @return std::coroutine_handle<> representing the active coroutine. + */ [[nodiscard]] constexpr std::coroutine_handle<> active_handle() const noexcept { return m_active_handle; } + /** + * @brief Get the current blocking state of this context + * + * This method returns the current blocking state that determines how the + * scheduler should handle this context. + * + * @return blocked_by enum value indicating the current blocking state + */ [[nodiscard]] constexpr auto state() const noexcept { return m_state; } - constexpr void active_handle(std::coroutine_handle<> p_active_handle) - { - m_active_handle = p_active_handle; - } - + /** + * @brief Check if the context has completed its operation + * + * This method determines whether the context is in a "done" state, meaning + * it has completed all operations and no longer needs to be scheduled. The + * stack memory of the context should be completely unused when this function + * returns `true`. + * + * @return true if the context is done, false otherwise + */ constexpr bool done() { return m_active_handle == noop_sentinel; } + /** + * @brief Cancel all operations on this context + * + * This method cancels all pending operations on this context. + */ void cancel(); + /** + * @brief Resume the active coroutine on this context + * + * This method resumes the currently active coroutine. It only has an effect + * if the context is not blocked by time, as time-blocking contexts must wait + * for their designated duration to elapse. + */ void resume() { // We cannot resume the a coroutine blocked by time. @@ -277,21 +522,50 @@ public: } } + /** + * @brief Get the amount of stack memory used by active coroutines + * + * This method returns how much stack space has been consumed by currently + * active coroutines. + * + * @return The number of `uptr` sized words used in the stack + */ [[nodiscard]] constexpr auto memory_used() const noexcept { return m_stack_pointer - m_stack.data(); } + /** + * @brief Get the total capacity of the stack memory + * + * This method returns the total size of the stack buffer in uptr words. + * + * @return The total capacity in uptr words + */ [[nodiscard]] constexpr auto capacity() const noexcept { return m_stack.size(); } + /** + * @brief Get the remaining stack memory available + * + * This method returns how much stack space is still available for new + * coroutine allocation. + * + * @return The number of `uptr` sized words used in the stack + */ [[nodiscard]] constexpr auto memory_remaining() const noexcept { return capacity() - memory_used(); } + /** + * @brief Virtual destructor for proper cleanup of derived classes + * + * This virtual destructor ensures that derived context classes are properly + * cleaned up when deleted through a base class pointer. + */ virtual ~context() = default; private: @@ -299,17 +573,49 @@ private: template friend class promise; + /** + * @brief Internal structure to track proxy context information + */ struct proxy_info { context* original = nullptr; context* parent = nullptr; }; + /** + * @brief Check if this is a proxy context + * + * This method determines whether the current context is acting as a proxy + * for another context. + * + * @return true if this is a proxy context, false otherwise + */ [[nodiscard]] constexpr bool is_proxy() const noexcept { return m_proxy.parent == nullptr; } + /** + * @brief Set the active coroutine handle for this context + * + * This method sets the coroutine that is currently running on this context. + * + * @param p_active_handle The coroutine handle to set as active + */ + constexpr void active_handle(std::coroutine_handle<> p_active_handle) + { + m_active_handle = p_active_handle; + } + + /** + * @brief Transition the context to a new blocking state + * + * This internal method transitions the context to a new blocking state and + * notifies the scheduler via do_schedule(). + * + * @param p_new_state The new blocking state to transition to + * @param p_info Additional information about the blocking condition + */ constexpr void transition_to(blocked_by p_new_state, block_info p_info = std::monostate{}) noexcept { @@ -317,6 +623,16 @@ private: schedule(p_new_state, p_info); } + /** + * @brief Allocate memory for a coroutine frame on the stack + * + * This method allocates space on the context's stack for a coroutine frame. + * It ensures that the allocation fits within the available stack space. + * + * @param p_bytes The number of bytes to allocate + * @return Pointer to the allocated memory location + * @throws bad_coroutine_alloc if there's insufficient stack space + */ [[nodiscard]] constexpr void* allocate(std::size_t p_bytes) { // The extra 1 word is for the stack pointer's address @@ -400,19 +716,70 @@ static_assert(sizeof(context) <= std::hardware_constructive_interference_size, "Context cannot be contained within a cache-line (as specified " "by std::hardware_constructive_interference_size)"); +/** + * @brief A proxy context that provides isolated stack space for supervised + * coroutines + * + * The proxy_context class allows creating a sub-context with its own stack + * space that is isolated from the parent context. This is particularly useful + * for implementing timeouts and supervision of coroutines. + * + * When a proxy context is created, it takes the remaining stack space from the + * parent context and ensures that the parent's stack is properly clamped to + * prevent overwrites. + */ export class proxy_context : public context { public: + /** + * @brief Delete copy constructor + * + * Proxy contexts cannot be copied as they manage unique stack memory. + */ proxy_context(proxy_context const&) = delete; + + /** + * @brief Delete copy assignment operator + * + * Proxy contexts cannot be copied as they manage unique stack memory. + */ proxy_context& operator=(proxy_context const&) = delete; + + /** + * @brief Delete move constructor + * + * Proxy contexts cannot be moved as they manage unique stack memory. + */ proxy_context(proxy_context&&) = delete; + + /** + * @brief Delete move assignment operator + * + * Proxy contexts cannot be moved as they manage unique stack memory. + */ proxy_context& operator=(proxy_context&&) = delete; + /** + * @brief Create a proxy context from an existing parent context + * + * This static method creates a new proxy context that uses a portion of the + * parent context's stack memory. The proxy takes control over the remaining + * stack space, effectively creating an isolated sub-context. + * + * @param p_parent The parent context to create a proxy from + * @return A new proxy_context instance + */ static proxy_context from(context& p_parent) { return { p_parent }; } + /** + * @brief Destructor for proxy_context + * + * The destructor cancels any remaining operations and properly restores + * the parent context's stack memory to its original state. + */ ~proxy_context() override { // Cancel any operations still on this context @@ -425,6 +792,14 @@ public: } private: + /** + * @brief Constructor for proxy_context + * + * This private constructor is used internally to set up the proxy context + * with isolated stack memory from a parent context. + * + * @param p_parent The parent context to create proxy from + */ proxy_context(context& p_parent) { m_active_handle = context::noop_sentinel; @@ -462,6 +837,10 @@ private: /** * @brief Forwards the schedule call to the original context * + * This method forwards scheduling notifications to the original context, + * ensuring that the parent context's scheduler is properly notified of + * state changes. + * * @param p_block_state - state that this context has been set to * @param p_block_info - information about the blocking conditions */ @@ -472,12 +851,53 @@ private: } }; +/** + * @brief A basic context implementation that supports synchronous waiting + * + * The basic_context class provides a concrete implementation of the context + * interface that supports synchronous waiting operations. It extends the base + * context with functionality to wait for coroutines to complete using a simple + * synchronous loop. + * + * NOTE: This class does not provide stack memory + * + * This context is particularly useful for testing and simple applications where + * a scheduler isn't needed, as it provides a way to wait for all coroutines to + * complete without requiring external scheduling. + * + * @note basic_context is designed for simple use cases and testing, not + * production embedded systems where strict memory management is required. + */ export class basic_context : public context { public: + // TODO(63): Add stack memory template argument + /** + * @brief Default constructor for basic_context + * + * Creates a new basic context with default initialization. + */ basic_context() = default; + + /** + * @brief Virtual destructor for proper cleanup + * + * Ensures that the basic context is properly cleaned up when deleted. + */ ~basic_context() override = default; + /** + * @brief Get the pending delay time for time-blocking operations + * + * This method returns the sleep duration that is currently pending for + * time-blocking operations. It's used by the sync_wait method to determine + * how long to sleep. + * + * @return The pending sleep duration + * + * @note This is an internal method used by the basic_context implementation + * to manage time-based blocking operations during synchronous waiting. + */ [[nodiscard]] constexpr sleep_duration pending_delay() const noexcept { return m_pending_delay; @@ -486,16 +906,29 @@ public: /** * @brief Perform sync_wait operation * - * @tparam DelayFunc + * This method waits synchronously for all coroutines on this context to + * complete. It uses the provided delay function to sleep for the required + * duration when waiting for time-based operations. + * + * @tparam DelayFunc The type of the delay function (must be invocable with + * sleep_duration parameter) * @param p_delay - a delay function, that accepts a sleep duration and - * returns void. + * returns void. + * + * @note This method is primarily intended for testing and simple applications + * where a synchronous wait is needed. It's not suitable for production + * embedded systems that require precise timing or real-time scheduling. */ void sync_wait(std::invocable auto&& p_delay) { while (active_handle() != context::noop_sentinel) { active_handle().resume(); - if (state() == blocked_by::time && m_pending_delay > sleep_duration(0)) { + if (state() == blocked_by::time) { + if (m_pending_delay == sleep_duration(0)) { + unblock_without_notification(); + continue; + } p_delay(m_pending_delay); m_pending_delay = sleep_duration(0); unblock_without_notification(); @@ -507,11 +940,15 @@ private: /** * @brief Forwards the schedule call to the original context * + * This method handles scheduling notifications for time-blocking operations. + * It stores the pending delay duration so that sync_wait can properly wait + * for it. + * * @param p_block_state - state that this context has been set to * @param p_block_info - information about the blocking conditions */ void do_schedule(blocked_by p_block_state, - block_info p_block_info) noexcept override + block_info p_block_info) noexcept final { if (p_block_state == blocked_by::time) { if (auto* ex = std::get_if(&p_block_info)) { @@ -523,64 +960,158 @@ private: // Ignore the rest and poll them... } + /** + * @brief The pending delay for time-blocking operations + * + * This member stores the sleep duration that is currently pending for + * time-blocking operations, allowing sync_wait to properly handle delays. + */ sleep_duration m_pending_delay{ 0 }; }; +/** + * @brief A RAII-style guard for exclusive access to a context + * + * The exclusive_access class provides a mechanism for managing exclusive + * access to a context, particularly in scenarios involving synchronization + * primitives like mutexes or semaphores. It ensures proper cleanup and + * unblocking when the guard goes out of scope. + * + * This is particularly useful for implementing resource management in + * coroutine-based systems where proper cleanup and blocking state + * transitions are required. + */ export class exclusive_access { public: + /** + * @brief Default constructor for exclusive_access + * + * Creates an uninitialized exclusive_access guard. + */ constexpr exclusive_access() = default; + + /** + * @brief Constructor that captures a context for exclusive access + * + * @param p_capture The context to capture for exclusive access + */ constexpr exclusive_access(context& p_capture) noexcept : m_context_address(&p_capture) { } + /** + * @brief Assignment operator to capture a new context + * + * @param p_capture The context to capture for exclusive access + * @return Reference to this exclusive_access instance + */ constexpr exclusive_access& operator=(context& p_capture) noexcept { m_context_address = &p_capture; return *this; } + + /** + * @brief Assignment operator to clear the context capture + * + * @param p_capture nullptr to clear the capture + * @return Reference to this exclusive_access instance + */ constexpr exclusive_access& operator=(nullptr_t) noexcept { m_context_address = nullptr; return *this; } + /** + * @brief Copy constructor for exclusive_access + * + * @param p_capture The exclusive_access instance to copy from + */ constexpr exclusive_access(exclusive_access const& p_capture) noexcept = default; + + /** + * @brief Copy assignment operator for exclusive_access + * + * @param p_capture The exclusive_access instance to copy from + * @return Reference to this exclusive_access instance + */ constexpr exclusive_access& operator=( exclusive_access const& p_capture) noexcept = default; + + /** + * @brief Move constructor for exclusive_access + * + * @param p_capture The exclusive_access instance to move from + */ constexpr exclusive_access(exclusive_access&& p_capture) noexcept = default; + + /** + * @brief Move assignment operator for exclusive_access + * + * @param p_capture The exclusive_access instance to move from + * @return Reference to this exclusive_access instance + */ constexpr exclusive_access& operator=(exclusive_access& p_capture) noexcept = default; + /** + * @brief Equality operator to check if this guard holds a specific context + * + * @param p_context The context to compare against + * @return true if this guard holds the specified context, false otherwise + */ constexpr bool operator==(context& p_context) noexcept { return m_context_address == &p_context; } + /** + * @brief Check if this guard is currently holding a context + * + * @return true if the guard has an active context, false otherwise + */ [[nodiscard]] constexpr bool in_use() const noexcept { return m_context_address != nullptr; } + /** + * @brief Check if this guard has an active context (bool conversion) + * + * This operator provides a way to check if the guard is currently active. + * + * @return true if the guard has an active context, false otherwise + */ [[nodiscard]] auto address() const noexcept { return m_context_address != nullptr; } + /** + * @brief Convert to bool (check if in use) + * + * This operator provides a way to check if the guard is currently active. + * + * @return true if the guard has an active context, false otherwise + */ [[nodiscard]] constexpr operator bool() const noexcept { return in_use(); } - // TODO(#29): Lease should return a guard variable that, on destruction, - // unblocks and clear itself. - constexpr void lease(context& p_capture) noexcept - { - m_context_address = &p_capture; - } - + /** + * @brief Set this guard as a blocking state for synchronization + * + * This method sets the specified context to be blocked by synchronization, + * effectively creating a dependency between contexts. + * + * @param p_capture The context to set as blocking by sync + * @return std::suspend_always to suspend the coroutine until resumed + */ constexpr std::suspend_always set_as_block_by_sync(context& p_capture) { if (in_use()) { @@ -589,6 +1120,12 @@ public: return {}; } + /** + * @brief Unblocks the associated context and clears this guard + * + * This method unblocks the context that was captured by this guard and + * clears the guard's reference to it. + */ constexpr void unblock_and_clear() noexcept { if (in_use()) { @@ -598,24 +1135,68 @@ public: } private: + /** + * @brief The address of the context being held, or nullptr if not in use + */ context* m_context_address = nullptr; }; +/** + * @brief I/O operation descriptor for async operations + * + * The io struct provides a way to describe I/O operations that can be awaited. + * It contains information about the expected duration for I/O completion, + * which can be used by schedulers to determine when to poll or reschedule + * coroutines waiting for I/O operations. + */ export struct io { + /** + * @brief Construct an io descriptor with a specified duration + * + * @param p_duration The expected duration for I/O completion (default: 0) + */ io(sleep_duration p_duration = sleep_duration{ 0u }) : m_duration(p_duration) { } + + /** + * @brief The expected duration for I/O completion + * + * This field represents the estimated time for an I/O operation to complete. + * It can be used by schedulers to determine appropriate polling intervals + * or scheduling decisions. + */ sleep_duration m_duration; }; +/** + * @brief Synchronization operation descriptor for async operations + * + * The sync struct provides a way to describe synchronization operations that + * can be awaited. It contains a reference to an exclusive_access guard, + * which is used to manage resource contention in coroutine-based systems. + */ export struct sync { + /** + * @brief Construct a sync descriptor with an exclusive access guard + * + * @param p_context The exclusive access guard that describes the sync + * operation + */ sync(exclusive_access p_context) : m_context(p_context) { } + + /** + * @brief The exclusive access guard for this synchronization operation + * + * This field holds the exclusive access information that describes the + * synchronization resource being waited for. + */ exclusive_access m_context; }; @@ -625,6 +1206,15 @@ export struct sync // // ============================================================================= +/** + * @brief The base promise class for coroutine operations + * + * The promise_base class provides the foundation for coroutine promise types. + * It handles memory allocation, coroutine state management, and integration + * with the async_context system. It's designed to work with stack-based + * allocation and provides the necessary infrastructure for coroutine frame + * management. + */ class promise_base { public: @@ -649,6 +1239,15 @@ public: return p_context.allocate(p_size); } + /** + * @brief Delete operator for coroutine promises + * + * This method handles cleanup of coroutine frames when they are destroyed. + * It restores the stack pointer to its previous position, effectively + * deallocating the memory used by the coroutine frame. + * + * @param p_promise Pointer to the promise being deleted + */ static constexpr void operator delete(void* p_promise) noexcept { // Acquire the pointer to the context stack memory from behind the coroutine @@ -668,38 +1267,75 @@ public: *stack_pointer_address = (static_cast(p_promise) - 1); } - // Constructor for functions accepting no arguments + /** + * @brief Constructor for promise_base + * + * @param p_context The context that this promise will run on + */ promise_base(context& p_context) : m_context(&p_context) { } - // Constructor for functions accepting arguments + /** + * @brief Constructor for promise_base with additional arguments + * + * @param p_context The context that this promise will run on + * @param p_args Additional arguments for the constructor + */ template promise_base(context& p_context, Args&&...) : m_context(&p_context) { } - // Constructor for member functions (handles 'this' parameter) + /** + * @brief Constructor for member functions (handles 'this' parameter) + * + * @param p_this The 'this' object for member function promises + * @param p_context The context that this promise will run on + */ template promise_base(Class&, context& p_context) : m_context(&p_context) { } - // Constructor for member functions with additional parameters + /** + * @brief Constructor for member functions with additional parameters + * + * @param p_this The 'this' object for member function promises + * @param p_context The context that this promise will run on + * @param p_args Additional arguments for the constructor + */ template promise_base(Class&, context& p_context, Args&&...) : m_context(&p_context) { } + /** + * @brief Get the initial suspend behavior for coroutines + * + * This method determines whether a coroutine should initially suspend + * when it starts executing. + * + * @return std::suspend_always to suspend the coroutine initially + */ constexpr std::suspend_always initial_suspend() noexcept { return {}; } + /** + * @brief Handle awaitable operations for sleep duration + * + * This method transforms sleep duration awaitables into appropriate blocking + * operations for the context. + * + * @param p_sleep_duration The sleep duration to await + * @return std::suspend_always to suspend the coroutine until resumed + */ template constexpr auto await_transform( std::chrono::duration p_sleep_duration) noexcept @@ -707,39 +1343,87 @@ public: return m_context->block_by_time(p_sleep_duration); } + /** + * @brief Handle awaitable operations for I/O + * + * This method handles I/O operations that should block until completion. + * + * @return std::suspend_always to suspend the coroutine until resumed + */ constexpr std::suspend_always await_transform() noexcept { m_context->block_by_io(); return {}; } + /** + * @brief Handle generic awaitables + * + * This method passes through any other awaitable operations without + * modification. + * + * @param p_awaitable The awaitable to pass through + * @return The original awaitable unchanged + */ template constexpr U&& await_transform(U&& p_awaitable) noexcept { return static_cast(p_awaitable); } + /** + * @brief Get the context associated with this promise + * + * @return Reference to the context that this promise runs on + */ constexpr auto& get_context() { return *m_context; } + /** + * @brief Get the continuation coroutine handle + * + * If the term "continuation" is confusing, another way of thinking about it + * as the "return address" of the calling coroutine. + * + * @return The coroutine handle for the continuation of this operation + */ constexpr auto continuation() { return m_continuation; } + /** + * @brief Set the continuation coroutine handle + * + * @param p_continuation The coroutine handle to set as continuation + */ constexpr void continuation(std::coroutine_handle<> p_continuation) { m_continuation = p_continuation; } + /** + * @brief Pop the active coroutine from the context stack + * + * This method removes the current coroutine from the context's active handle + * and returns its continuation. + * + * @return The coroutine handle for the continuation of this operation + */ constexpr std::coroutine_handle<> pop_active_coroutine() { m_context->active_handle(m_continuation); return m_continuation; } + /** + * @brief Cancel this coroutine operation + * + * This method cancels the current coroutine operation by setting its state + * to cancelled and cleaning up resources. + */ void cancel() { // Set future state to cancelled @@ -751,6 +1435,12 @@ public: } protected: + /** + * @brief Type alias for cancellation function pointer + * + * This type represents the function signature used for cancellation + * callbacks. + */ using cancellation_fn = void(void*); // Consider m_continuation as the return address of the coroutine. The @@ -764,12 +1454,23 @@ protected: export template class future; +/** + * @brief Type alias for conditional monostate or type T + * + * This alias provides a convenient way to represent either std::monostate (for + * void) or the actual type T, depending on whether T is void. This is needed + * for std::variant which cannot have a void type as a member. + * + * @tparam T The type to conditionally wrap with monostate + */ template using monostate_or = std::conditional_t, std::monostate, T>; /** * @brief Represents a finished future of type void * + * This struct is used as one of the states in a future's state variant to + * represent that a void future has completed successfully. */ struct cancelled_state {}; @@ -786,6 +1487,13 @@ struct busy_state /** * @brief Defines the states that a future can be in * + * This type alias defines the possible states a future can be in: + * + * - Running (suspended at await point) + * - Value (completed with a value) + * - Cancelled (explicitly cancelled) + * - Exception (completed with an exception) + * * @tparam T - the type for the future to eventually provide to the owner of * this future. */ @@ -797,15 +1505,41 @@ using future_state = std::exception_ptr // 3 - exception >; +/** + * @brief Final awaiter for coroutine completion + * + * The final_awaiter is used to handle the final suspension point of a + * coroutine. It ensures proper cleanup and continuation handling when a + * coroutine completes. + * + * @tparam Promise The promise type for the coroutine + */ template struct final_awaiter { + /** + * @brief Check if the awaiter is ready to resume + * + * @return false - always returns false to ensure await_suspend is always + * called. + */ constexpr bool await_ready() noexcept { return false; } - std::coroutine_handle<> await_suspend( + /** + * @brief Suspend the coroutine when it completes + * + * This method handles the final suspension point and ensures proper + * continuation of the calling coroutine. + * + * @param p_completing_coroutine The coroutine handle that is completing + * @return The coroutine handle to resume next, which is a symmetryic transfer + * from this completing coroutine to its continuation (what called it + * originally). + */ + constexpr std::coroutine_handle<> await_suspend( std::coroutine_handle p_completing_coroutine) noexcept { // The coroutine is now suspended at the final-suspend point. @@ -823,14 +1557,32 @@ struct final_awaiter return next_to_run; } - void await_resume() noexcept + /** + * @brief Handle resume after completion + * + * This method is called when the awaiter resumes, but does nothing. + */ + constexpr void await_resume() noexcept { } }; +/** + * @brief Base class for promise return handling + * + * This class provides the infrastructure for handling return values from + * coroutines in the promise system. + * + * @tparam T The type of value to return + */ template struct promise_return_base { + /** + * @brief Handle return value for non-void futures + * + * @param p_value The value to return from the coroutine + */ template void return_value(U&& p_value) noexcept requires std::is_constructible_v @@ -839,20 +1591,45 @@ struct promise_return_base m_future_state->template emplace(std::forward(p_value)); } + /** + * @brief Pointer to the future state that should be set at future + * construction. + */ future_state* m_future_state; }; +/** + * @brief Specialization of promise_return_base for void futures + * + * This specialization handles return values for void futures. + */ template<> struct promise_return_base { + /** + * @brief Handle return void for void futures + */ void return_void() noexcept { *m_future_state = std::monostate{}; } + /** + * @brief Pointer to the future state that should be set at future + * construction. + */ future_state* m_future_state; }; +/** + * @brief Promise class for coroutine operation handling + * + * The promise class is responsible for managing the lifecycle and state of + * coroutine operations. It handles memory allocation, exception propagation, + * and future state management. + * + * @tparam T The type of value this promise will eventually provide + */ export template class promise : public promise_base @@ -866,25 +1643,64 @@ public: friend class future; + /** + * @brief Get the final awaiter for coroutine completion + * + * @return The final_awaiter that handles completion of this coroutine + */ constexpr final_awaiter> final_suspend() noexcept { return {}; } + /** + * @brief Handle unhandled exceptions in coroutines + * + * This method is called when a coroutine throws an exception that isn't + * handled within the coroutine itself. + */ void unhandled_exception() noexcept { *promise_return_base::m_future_state = std::current_exception(); } + /** + * @brief Set future object associated with this promise to cancelled state + * + * This static method is used to cancel a promise by setting its state to + * cancelled_state. The exact promise type information is type erased and + * saved into the promise_base such that the `context` class can safely set + * its future objects to a cancelled state. + * + * @param p_self Pointer to the promise to cancel + */ static void cancel_promise(void* p_self) { auto* self = static_cast*>(p_self); *self->m_future_state = cancelled_state{}; } + /** + * @brief Get the return object for this promise + * + * This method creates and returns a future that represents the result of + * this coroutine operation. + * + * @return The future representing this coroutine's result + */ constexpr future get_return_object() noexcept; }; +/** + * @brief Represents a future value that can be awaited + * + * The future class represents the result of an asynchronous operation. It can + * hold either a value, an exception, or be in progress (waiting for + * completion). Futures are the primary way to manage asynchronous operations in + * this library. + * + * @tparam T The type of value that this future will eventually hold + */ export template class future { @@ -900,6 +1716,9 @@ public: * @brief Default initialization for a void future * * This future will considered to be done on creation. + * + * @note For void futures, the state is initialized to std::monostate, + * indicating completion with no return value. */ future() requires(std::is_void_v) @@ -914,6 +1733,11 @@ public: * passed into this. * * @tparam U - type that can construct a type T (which includes T itself) + * @param p_value The value to initialize the future with + * + * @note This constructor creates a completed future containing the provided + * value. The future will be in the "done" state with the value stored + * internally. */ template constexpr future(U&& p_value) noexcept @@ -922,6 +1746,17 @@ public: m_state.template emplace(std::forward(p_value)); }; + /** + * @brief Move constructor for future + * + * Transfers ownership of the asynchronous operation from another future. + * + * @param p_other The future to move from + * + * @note After moving, the source future will be left in a valid but + * unspecified state. The moved-to future will contain the same asynchronous + * operation or result. + */ constexpr future(future&& p_other) noexcept : m_state(std::exchange(p_other.m_state, std::monostate{})) { @@ -933,6 +1768,18 @@ public: } } + /** + * @brief Move assignment operator for future + * + * Transfers ownership of the asynchronous operation from another future. + * + * @param p_other The future to move from + * @return Reference to this future + * + * @note After moving, the source future will be left in a valid but + * unspecified state. The moved-to future will contain the same asynchronous + * operation or result. + */ constexpr future& operator=(future&& p_other) noexcept { if (this != &p_other) { @@ -947,6 +1794,12 @@ public: return *this; } + /** + * @brief Destruct future + * + * If the future contins a coroutine handle on destruction, then cancel is + * called on the associated context. + */ constexpr ~future() { if (std::holds_alternative(m_state)) { @@ -980,6 +1833,11 @@ public: * @return true - this operation is finished and either contains the value of * type T, an exception_ptr, or this future is in a cancelled state. * @return false - operation has yet to completed and does have a value. + * + * @note A future is considered "done" when it has either completed + * successfully, encountered an exception, or been cancelled. This method can + * be used to check if it's safe to extract the result or handle the + * completion state. */ [[nodiscard]] constexpr bool done() const { @@ -1000,7 +1858,7 @@ public: } /** - * @brief Reports if this async object has finished with an value + * @brief Returns true if this future contains a value * * @return true - future contains a value * @return false - future does not contain a value @@ -1015,6 +1873,10 @@ public: * * @return Type - reference to the value from this async operation. * @throws std::bad_variant_access if `has_value()` return false + * + * @note This method should only be called when `has_value()` returns true. + * Calling this method on a future that doesn't contain a value will throw + * std::bad_variant_access. */ [[nodiscard]] constexpr monostate_or& value() requires(not std::is_void_v) @@ -1083,6 +1945,19 @@ public: } }; + /** + * @brief Provides the awaitable interface for use with `co_await` + * + * This method enables the future to be used in `co_await` expressions, + * allowing other coroutines to wait for this future's completion. + * + * @return awaiter - An awaiter object that handles the suspension and + * resumption of coroutines awaiting this future. + * + * @note The awaiter will suspend the calling coroutine until this future + * completes, then resume with either the result value or an exception. The + * future will never be cancelled. + */ [[nodiscard]] constexpr awaiter operator co_await() noexcept { return awaiter{ *this }; @@ -1108,12 +1983,30 @@ private: }; /** - * @brief An async task is an async operation that performs some work but - * doesn't return a value. + * @brief Represents an async task (void future) + * + * The task type is an alias for future, representing an asynchronous + * operation that doesn't return a value. It's used for operations like + * logging, I/O operations, or other async actions that don't need to return + * a result. * + * @note Task is equivalent to future and serves as a convenient alias + * for void-returning asynchronous operations. */ export using task = future; +/** + * @brief Get the return object for this promise + * + * This method creates and returns a future that represents the result of + * this coroutine operation. + * + * @return The future representing this coroutine's result + * + * @note This method is called by the coroutine framework when a coroutine + * completes. It creates a future that can be awaited by code that called + * the coroutine. + */ template constexpr future promise::get_return_object() noexcept { @@ -1123,6 +2016,14 @@ constexpr future promise::get_return_object() noexcept return future{ handle }; } +/** + * @brief Cancel all operations on this context + * + * This method cancels all pending operations on the context. + * + * @note This method is called internally by the context destructor to ensure + * proper cleanup of all pending asynchronous operations. + */ void context::cancel() { while (not done()) { diff --git a/tests/util.cppm b/tests/util.cppm index 499136e..4d5c043 100644 --- a/tests/util.cppm +++ b/tests/util.cppm @@ -41,7 +41,7 @@ export { bool scheduled_called_once = false; }; - struct test_context : public async::basic_context + struct test_context : public async::context { std::shared_ptr info; std::array m_stack{};