Coroutine Architecture
Relevant source files
The following files were used as context for generating this wiki page:
HXLibs implements a modern C++20 coroutine framework focused on high-performance asynchronous I/O and task scheduling. At its core, the architecture separates high-level task management from low-level platform-specific event driving. It provides abstractions for asynchronous disk and network operations, timer management, and serial execution, all integrated into a unified event loop.
The system uses a primary EventLoop to orchestrate various drivers, including io_uring on Linux and IOCP on Windows. This architecture enables developers to write non-blocking code that appears synchronous, facilitating high-concurrency network servers and efficient file processing.
Sources: include/HXLibs/coroutine/loop/EventLoop.hpp, include/HXLibs/coroutine/task/Task.hpp
Core Task Types
The architecture defines specific task structures to handle different lifecycle and execution requirements.
Task
Task<T> is the fundamental coroutine object for general asynchronous operations. It follows a lazy-start model where execution begins only when explicitly resumed or awaited. It manages a coroutine_handle and a promise_type. The destructor automatically destroys the handle unless a specific StopAwaiter<false> strategy is used.
Sources: include/HXLibs/coroutine/task/Task.hpp:33-54
AioTask
AioTask represents a low-level asynchronous I/O operation. Unlike standard tasks, AioTask directly interacts with the platform's kernel submission queue entries (SQEs) or overlapped structures. It provides specific methods for file and socket operations, such as prepRead, prepWrite, prepAccept, and prepConnect.
Sources: include/HXLibs/coroutine/task/AioTask.hpp:35-155
RootTask
A RootTask typically serves as the entry point for coroutines or detached background operations. It manages the top-level execution flow and is often used by ConnectionHandler to process new network connections.
Sources: include/HXLibs/net/server/ConnectionHandler.hpp:36-42
Task Relationship Diagram
The following diagram illustrates the relationship between the core task types and the coroutine handle management.
Event Loop and Driving Engines
The EventLoop is the central orchestrator that keeps the program running and handles asynchronous notification.
EventDrive Mechanics
The EventLoop selects a platform-specific EventDrive engine at compile-time:
- Linux: Uses
IoUring. It manages a circular buffer (io_uring) for submitting requests and receiving completions. - Windows: Uses
Iocp(I/O Completion Port). It identifies completed operations viaGetQueuedCompletionStatusEx.
The engine maintains a count of pending operations (_numSqesPending) and a list of coroutine handles to resume upon completion.
Sources: include/HXLibs/coroutine/loop/EventLoop.hpp:94-250
Execution Flow
The run() method processes operations in a continuous loop:
- Executes the
TimerLoopto handle expired timers and obtain a timeout value. - If the
EventDrive(io_uring/IOCP) has pending tasks, it callsrun(timeout)to block for completions. - If no I/O is pending but a timeout exists, it puts the thread to sleep.
- Exit occurs when no drivers have pending work.
Asynchronous I/O Operations
The AioTask interface provides a consistent API for network and file system access across platforms.
| Operation Category | Methods | Description |
|---|---|---|
| File I/O | prepRead, prepWrite, prepOpenat, prepClose | Reads/writes to disk fds at specific offsets. |
| Network Setup | prepSocket, prepAccept, prepConnect | Initializes sockets and establishes connections. |
| Network I/O | prepRecv, prepSend | Transfers data over network buffers. |
| Timed I/O | prepLinkTimeout, linkTimeout | Constraints an I/O task with a maximum duration. |
Sources: include/HXLibs/coroutine/task/AioTask.hpp:95-200, include/HXLibs/coroutine/task/AioTask.hpp:389-535
High-level File Abstraction: AsyncFile
The AsyncFile class provides a user-friendly wrapper around AioTask for disk operations. It maintains file state such as _offset and _fd.
- open: Initializes platform-specific structures (
prepOpenatorCreateFileA). - read/write: Submits I/O requests and maintains the file pointer offset.
- readAll: Asynchronously reads the entire file into a string buffer using a loop.
- close: Releases the file descriptor/handle through the event loop.
Sources: include/HXLibs/utils/FileUtils.hpp:116-248
Scheduling and Synchronization
The architecture includes specialized executors to manage task concurrency.
SerialScheduler
The SerialScheduler ensures that tasks within a specific sequence execute one at a time. It uses a std::queue to store pending tasks. When a task is added via serial(), if the scheduler is idle, it starts execution immediately. If a task is already running, it queues the new task and suspends the caller.
Sources: include/HXLibs/coroutine/executor/_SerialScheduler.hpp:109-130
Execution Sequence Diagram
Synchronization with Non-Coroutine Contexts
To bridge the gap between synchronous main() functions and asynchronous event loops, EventLoop provides synchronization methods:
- sync(T&& mainTask): Starts the coroutine, enters the event loop, and blocks until the task completes. It rethrows any exceptions caught during execution.
- trySync(T&& mainTask): Similar to
sync, but returns acontainer::Try<Res>containing either the result or an exception pointer. - start(T& mainTask): Manually resumes a coroutine handle to initiate execution without entering the loop blocking logic.
Sources: include/HXLibs/coroutine/loop/EventLoop.hpp:325-375