Skip to content

Repository files navigation

uringloop

A Python implementation of a liburing-based proactor event loop for asyncio, designed to follow Python standard library conventions. The implementation is written primarily in pure Python, using CFFI only for wrapping the liburing interface.

  • Implements a Proactor pattern similar to Windows' IOCP (reference implementation)
  • Follows Python's standard asyncio implementation patterns

⚠️ Note: This is currently experimental and not yet stable

Goals

  • Provide a primarily Python (with CFFI, maybe C extension in future) implementation of a liburing-based event loop
  • Maintain full compatibility with standard asyncio APIs
  • Follow Python standard library implementation patterns

Requirements

  • Linux 5.19+ kernel

  • Python 3.12+

  • A C compiler and make when installing from source. The pinned liburing sources are included and built automatically; a system liburing-dev package is not required.

Installation

  • Using uv:
uv add uringloop
  • Using pip:
pip install uringloop

Only a source distribution is currently published, so the native extensions and bundled liburing sources are compiled during installation.

Quick Start

Pass the loop factory to asyncio.run to use the io_uring-based event loop:

import asyncio

from uringloop import IoUringProactorEventLoop


async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    addr = writer.get_extra_info('peername')
    print(f"Connection from {addr}")

    while True:
        data = await reader.read(1024)
        if not data:
            break
        print(f"Received: {data.decode()}")
        writer.write(data)
        await writer.drain()

    print(f"Connection with {addr} closed")
    writer.close()


async def start_server():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)
    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()


async def tcp_client(message: str):
    reader, writer = await asyncio.open_connection(
        '127.0.0.1', 8888)

    print(f'Send: {message!r}')
    writer.write(message.encode())
    await writer.drain()

    data = await reader.read(100)
    print(f'Received: {data.decode()!r}')

    print('Closing connection')
    writer.close()
    await writer.wait_closed()


async def main():
    # Start the server in the background
    server_task = asyncio.create_task(start_server())

    # Give the server a moment to start
    await asyncio.sleep(1)

    # Run the client
    await tcp_client('Hello, World!')

    # Cancel the server task (otherwise it runs forever)
    server_task.cancel()
    try:
        await server_task
    except asyncio.CancelledError:
        pass


if __name__ == "__main__":
    asyncio.run(main(), loop_factory=IoUringProactorEventLoop)

the result would looks like

Serving on ('127.0.0.1', 8888)
Send: 'Hello, World!'
Connection from ('127.0.0.1', 52306)
Received: Hello, World!
Received: 'Hello, World!'
Closing connection
Connection with ('127.0.0.1', 52306) closed

An event loop policy is also available for code that still uses the (deprecated since Python 3.14) policy system:

import asyncio

from uringloop import IoUringProactorEventLoopPolicy

asyncio.set_event_loop_policy(IoUringProactorEventLoopPolicy())
asyncio.run(main())

Contributing

Contributions are welcome! Please open issues or pull requests for any bugs or feature requests.

License

This project is licensed under the terms of the MIT license.

TODO

  • use registered fd and buffer
  • add signal handling
  • refactor e2e test
  • benchmark
  • figure out a way to do unit/integration test

About

A Python implementation of a liburing-based proactor event loop for asyncio.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages