Architecture info¶
Development principles¶
- Strong layers between Python interface and C-functionality
- Trying to stick to Domain Driven Development
🟢 If you dont know what io_uring is about, what are Fixed and Provided buffers, multishot stream strategy - read explanation here first
Introduction¶
io_uring¶
uringio is written natively as C extension for CPython and brings the new event loop, based on liburing library - main lib to use io_uring.
io_uring is an alternate for epoll and other reactor-based systems.
Domains¶
- Ring
- Event loop
- Reader
- Registry
- Ops
- Buffer
- ExecutionContext
- Timer
- Signals
Main challenge to bring io_uring in Python was to connect Python event loop and io_uring.
-
From
io_uringside main thing isringitself. Ring contains two rings actually -Submission QueuewithSQE(Eis forevent) andCompletion QueuewithCQE. It is really important to understand this concepts, but it all really gives us only one domain -ring. There is plan to bring interface to set some ring parametres in next version. -
From Python side there is main object too -
Event Loop. Loop is complicated, and its complicity is shown in code: byloopdomain withUringioLoopobject and byreaderdomain. -
Readeris part of the loop that reads result of I/O multiplexing mechanism(the output part). In our case(io_uring) it reads directly result of operations, but when you work withepollit reads signal about socket readiness. -
Registryis a storage of reference to operations memory and operations metadata, that keep objects alive until they would be mapped withCQEs insideReader. -
Next domain is
Ops, which containsFileandSocketobjects. This domain is mirrorly presented in both c-layer and python-layer. Its purpose is to be an API of system operations. -
To read and write from/to
FilesandSokets, we need buffers. And there is separateBufferdomain for this purpose. Users can use operations with their own buffers or rely onuringiofunctionality of buffer creation. To separate this domain in code there isBufferPayloadstruct. -
But also
io_uringgives us some workarounds for buffers and operation handling. There is three categories:Buffer Mode,TransferModeandStreamStrategy. WhileBufferModeis an established inio_uringandliburingdefinition,TransferModeandStreamStrategyare not so, but in some places in internet they are used like that. \ To use this optimization inuringio, we are just initializing this modes inside context manager. For each of mode there is dedicated context manager, but also one more context manager to set them all at once -ExecutionContext, and this is the name of this domain. So: Buffer Modes. Different types of optimizations around buffers, mainly around buffer ownership. Without optimizations, user is sending buffers with every syscall.Fixed- On initial stage user is giving his buffers, or relies onuringio. Then, for every operationio_uringusing this fixed buffers. Its onuringioside to handle buffer indexes(FixedBufferIdxRegistry).Provided- On initial stage its the same as forFixed, butio_uringhandles even buffer indexes.Buffer Ring- Modern optimization ofProvidedmode. Note,io_uringprovides functionality to push or pop buffers from ring on the run, but support of this feature would be realized in futureuringioversions.
Transfer Mode. At now there is only one optimization, but we need to create category for it(also in next version there would one more category):Zero Copy- Zero copy is concept for ops to reduce CPU load. This isio_uringimplementation of this concept.
-
Stream Strategy- Same thing with motivation for existence of this category, and there is only one optimization:Multishot- Allows oneSubmission Queue Entryto generate multipleCompletion Queue Events. It is poorly supported and debugged feature for now, so be careful. (v0.4.4)
-
Timer- as we are waiting for kernel to complete the operation, we can set timeouts. That and performing separate async timer operation are purpose of this layer. -
Signals- to process cancellation commands, like ctrl-z.
Structure¶
Structure of this project is trying to be simple - we have pure c-layer and pure python-layer and layer in between.
- C-layer - Functionality written entirely in C and basically wrappers aroung liburing API
- Python-layer - By analogy it is layer, written with usage of CPython API and CPython objects.
- Layer in between - for now here is really only one thing - registry. It is container to hold objects in between. I wish i could say that in between C-layer and Python-layer, but the meaning is not so. We are working with async nature and giving control of the operations to kernel, so we can do our things. To map the result of kernel with what was intended we need some sort of storage - this is registry.
More project structure explanation. If you're not interested in contributing, you can jump to next section
Ring¶
Code is inside /src/ring folder, where you can find:
- ring.c - contains initializer and destroyer of ring; and part of ring initialization separated into its own method.
- sqe_helper.c - contains little wrapper for general SQE creation.
All methods inside ring.c are only part of the loop initialization and destruction process.
SQE helper is spread across C-layer objects only.
Lays entirely in C-layer.
Loop¶
Code is inside src/python_api/loop, where you can find:
- loop.h - Defines
UringioLoopobject. - loop.c - API for
UringioLoopand redefinitions ofBaseEventLoopmethods. - shutdown.c - Contains methods for shutting down the
UringioLoop. - helpers.c - Contains specific helpers for redefinitions of
BaseEventLoopmethods. - future.c - Contains little wrapper for future creation. In next versions there will be
FuturePool.
Initialization of most objects is happening inside
UringioLoopconstruction and initialization, so desctruction too. Lays entirely in Python-layer.
Reader¶
Code is inside src/python_api/reader, where is only:
- on_ready.h - Handler of
Completion Queue- serializingCQEresults and setting asyncio feature result by mapping them viaregistry.
Lays entirely in Python-layer.
Registry¶
Code is inside src/registry, where you can see:
- registry.h - Defines
RequestRegistryandRequestSlotobjects, that are spread around whole project, but mainly is Python API functions. They are internal objects, and its important to know what is inside these objects. - registry.c - Internal API for registry, works in O(1) Time complexity.
The only domain intended to be in between c and python layers.
OPS¶
Code is inside src/ops/ AND src/python_api/ops. There you can see Socket subdomain and File subdomain.
Socket:- src/ops/sockets/
- sockets.c - C API handlers for socket OPS
- fixed.c - C API handlers for socket OPS in Fixed buffer mode
- buffer_select.c - C API handlers for socket OPS in Provided and Buffer Ring buffer modes
- multishot.c - C API handlers for socket multishot OPS.
- zerocopy.c - C API handlers for socket zerocopy OPS.
- src/python_api/ops/sockets/
- sockets.h - Defines
Socketobject asUringioSocketstruct. - sockets.c - Public API handler for all socket operations.
- socket_ops_dispatcher.c - Helper functions to dispatch between same socket operations in different execution context.
- _helpers.c - Some utility helpers, mostly validators.
- sockets.h - Defines
- src/ops/sockets/
File:- src/ops/files/
- files.c - C API handler for file OPS
- fixed.c - C API handler for file OPS in Fixed buffer mode
- buffer_select.c - C API handler for file OPS in Provided and Buffer Ring modes
- zerocopy.c - C API handler for file zerocopy OPS
- src/python_api/ops/files/
- files.h - Defines
Fileobject asUringioFilestruct. - enums.c - Defines
ResolveFlags,StatxFlagsandStatxMaskEnums foropenoperation and for now not implementedstatxoperation. - files.c - Public API handler for all file operations
- files_ops_dispatcher.c - Helper functions to dispatch between same file operations in different execution context
- _helpers.c - Intended for some utility functions, for now there is only one for raising chain of exceptions
- files.h - Defines
- src/ops/files/
This domain is mirrored in both c-layer and python-layer
Buffer¶
Code is inside src/python_api/buffers/ and src/buffer_controllers/, where we can see:
- src/python_api/buffers/
- buffers.h - No external objects to use from Python is here, but there is one important internal object, wrapper of buffers -
BufferPayload. - buffers.c - Internal functions for different to wrap buffers in
BufferPayloadobject. Some buffers are user inputs, other created by system. Buffer can belinearorvectored(iovecs), butBufferPayloadcan store both.
- buffers.h - No external objects to use from Python is here, but there is one important internal object, wrapper of buffers -
- src/buffer_controllers/
- buffer_controllers.h - Define IndexRegistry object for Fixed buffer mode -
FixedBufferIdxRegistry. - buffer_index.c - Internal API for
FixedBufferIdxRegistry, works in O(1) Time complexity and built same way as mainRegistry. - buffer_modes.c - There is
io_uringconcept -BufferMode. This is handler to create and destroy all types ofBufferMode.
- buffer_controllers.h - Define IndexRegistry object for Fixed buffer mode -
This domain is in both c-layer and python-layer, but not mirrored between themselves.
ExecutionContext¶
Code is inside src/python/api/execution_context/
- execution_context.h - Provides important internal struct
ExecutionContextand defines 4 Python object to operate inside context manager -BufferModeCtx,StreamStrategyCtx,TransferModeCtx,ExecutionContextCtx. All 4 are running execution context or only one dimension of execution context. - execution_context.c - Public API of context managers -
BufferModeCtx,StreamStrategyCtx,TransferModeCtx,ExecutionContextCtx. - contextvar.c - To run execution context there is solution to use Python's
ContextVar. Here lays controller of specialContextVarforExecutionContext. - enums.c - Defines
ExecutionContextspecific Python Enums:BUFFER_MODE,STREAM_STRATEGY,TRANSFER_MODEandPAYLOAD_TYPE. - execution_context_enums.c - Internal C Enums, separated in module to get rid of circular dependency.
Lays entirely in Python-layer.
Timer¶
Code is inside src/timer and src/python_api/timer, where we can see:
- src/timer/
- timer.c - C handler for timer operation and for timeout operation marking.
- src/python_api/timer/
- timer.c - Public API handler for timer operation.
- _helpers.c - Input validators for timer operation and timeout parameters.
This domain is partly mirrored in both c-layer and python-layer
Signals¶
Code is inside src/python_api/signals and src/signals, where we can see everything related. There is space to separate on CPython-layer and C-layer
Python objects¶
- Main:
- UringioLoop - Main object of library, by the way, users would not interact a lot with it. With initialization of loop there is initialization of all io_uring objects. Child of
BaseEventLoopobject with redefinition of loop's reader. For now custom reader is not really supporting every async operation throughio_uringrings. - File, internally is
UringioFile. There is its own methods for every file operation inio_uring, so this object just use them and is compitable withUringioLoop. Every operation is supported inuringio, andio_uringspecific operations, likereadwithBUFFER_SELECTBuffer Mode. - Socket, internally is
UringioSocket. There are its own methods for every socket operation inio_uring, so this object just use them and is compitable withUringioLoop. Almost every(for now) operation is supported inuringio, andio_uringspecific operations, likeacceptwithMULTISHOTStreamStrategy.
- UringioLoop - Main object of library, by the way, users would not interact a lot with it. With initialization of loop there is initialization of all io_uring objects. Child of
- Helpers:
- BufferModeCtx - Special object to implement Pythons context manager of
BufferMode - StreamStrategyCtx - Special object to implement Pythons context manager of
StreamStrategy - TransferModeCtx - Special object to implement Pythons context manager of
TransferMode - ExecutionContextCtx - Special object to implement special context manager, that includes inside itself previous 3.
- BufferModeCtx - Special object to implement Pythons context manager of
- Enums:
- BUFFER_MODE - Enumeration of all possible
buffer modes variations, includingNORMALmode. - STREAM_STRATEGY - Enumeration of all possible
stream strategys variations, includingNORMALmode. - TRANSFER_MODE - Enumeration of all possible
transfer modes variations, includingNORMALmode. - PAYLOAD_TYPE - Enumeration of buffer types - could be
LINEAR,IOVECSor both. Users mainly don't need this, as it detect automatically. But in some cases it could be useful. - Resolve Flags - Is using for
File.open()method only. - Statx Flags - Flags for staticx operation. I tried to implement statx operation but faced some troubles, and in favour of time decided to delete method and implement it later, but to leave this enums for it.
- StatxMask - Flags for statx operation. I tried to implement statx operation but faced some troubles, and in favour of time decided to delete method and implement it later, but to leave this enums for it.
- BUFFER_MODE - Enumeration of all possible