71 Random Access Store (RAS)

Design criteria

An asynchronous version of IRAS should satisfy the following criteria:

  1. Be compatible with optimal performance on both Windows and Linux.
  2. Be elegant and simple.
  3. Be fully asynchronous when reading and writing serial elements and the root block.
  4. Be safe.
  5. Promote concurrency if and only if concurrency makes sense.

Native foundation

Windows I/O completion ports

Windows supports asynchronous file operations through overlapped I/O. A file is opened with FILE_FLAG_OVERLAPPED, and each operation is submitted with an OVERLAPPED structure that identifies the operation and its file offset. The operation may complete immediately or remain pending while the calling thread continues to do other work.

An I/O completion port (IOCP) provides a completion queue to which file handles can be associated. When an overlapped operation finishes, Windows places a completion packet on the port. One or more threads can retrieve packets from the port, with the configured concurrency limit controlling how many associated threads may run at once. A single completion port can service operations for many file handles, allowing a bounded set of threads to process a large number of outstanding operations.

A completion packet reports the completed operation, its status and the number of bytes transferred. Completion order is not necessarily submission order. The application must retain the OVERLAPPED structure and all buffers referenced by the operation until completion has been reported.

Linux io_uring

Linux io_uring uses a pair of shared-memory ring buffers. The application places requests in the submission queue and the kernel places their results in the completion queue. This arrangement reduces the number of system calls and permits requests and completions to be processed in batches.

Each submission queue entry describes an operation and contains a user-defined value that is returned with its completion queue entry. Operations include file reads, writes, synchronization and many other forms of I/O. Requests are normally independent and may execute or complete out of order. Linked requests can be used when a sequence of operations has an explicit dependency.

io_uring can register files and buffers in advance, reducing per-operation setup and memory-management overhead. Buffers used by outstanding operations must remain valid until the corresponding completion entries have been processed.

Creation and shared I/O resources

A native asynchronous RAS needs an environment that submits I/O and dispatches its completions. On Windows this includes an I/O completion port and threads that retrieve completion packets. On Linux it includes one or more io_uring instances and code that submits SQEs and drains CQEs. Other implementations may use an executor, an I/O context, a platform event loop or a shared worker facility for operations that have no native asynchronous equivalent.

These resources should not normally be created separately for every RAS or LSS. Many Windows file handles can share one I/O completion port, and a Linux I/O service can process operations for many file descriptors. A process-level service can therefore use a bounded collection of threads and I/O contexts for many open stores:


struct IAsyncIoService;

std::unique_ptr<IRAS> CreateFileRAS(
    IAsyncIoService& ioService,
    ConstStringZ path,
    const RASSettings& settings);

This signature is illustrative. The important design point is that the platform RAS implementation, or a factory that creates it, receives the shared completion environment. Platform concepts such as IOCP handles, io_uring rings and completion threads remain behind that abstraction and are not exposed to the LSS.

An already constructed RAS is passed into the LSS when the store is created or opened:


std::unique_ptr<ILss> CreateOrOpenLss(
    std::unique_ptr<IRAS> ras,
    const LssSettings& settings);

This makes the storage implementation an explicit dependency of the LSS. It also permits the same LSS implementation to use a native file RAS, an in-memory RAS, a test or fault-injection RAS, a WebAssembly storage implementation or another future backend without giving the LSS knowledge of the backend's scheduling mechanism.

A convenience API that accepts a filename and constructs the RAS internally can instead receive an environment or RAS factory that is already bound to the shared I/O service. It should not require platform-specific completion resources to be threaded through the logical LSS implementation.

Lifetime

The shared I/O service must outlive every RAS that uses it, and a RAS and its callback receiver must outlive all native operations submitted through that RAS. Closing an LSS stops new logical work, drains or cancels its outstanding RAS operations, and ensures that no further callback can reach the LSS before destroying the RAS. The shared service remains available to other stores and is stopped only after all dependent RAS instances have closed.

The LSS event-driven state machine does not necessarily require its own dedicated executor or thread. RAS completions and application requests can be admitted to a thread-safe LSS event queue whose events are processed sequentially by a run-to-completion drain owner. A separate executor may be provided when application notifications require thread affinity, but it is not an inherent requirement of the RAS or LSS design.