Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A Python implementation of a liburing-based proactor event loop for asyncio, des

## Goals

- Provide a primarily Python (with CFFI, maybe C extention in future) implementation of a liburing-based event loop
- 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

Expand Down Expand Up @@ -46,7 +46,7 @@ Pass the loop factory to `asyncio.run` to use the io_uring-based event loop:
```python
import asyncio

from uringloop import IouringProactorEventLoop
from uringloop import IoUringProactorEventLoop


async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
Expand Down Expand Up @@ -110,7 +110,7 @@ async def main():


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

```

Expand All @@ -131,9 +131,9 @@ An event loop policy is also available for code that still uses the (deprecated
```python
import asyncio

from uringloop import IouringProactorEventLoopPolicy
from uringloop import IoUringProactorEventLoopPolicy

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

Expand Down
19 changes: 6 additions & 13 deletions tests/e2e/loop/conftest.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import asyncio
import os
import tempfile
from typing import cast

import pytest
import pytest_asyncio

from uringloop.loop import IouringProactorEventLoop, IouringProactorEventLoopPolicy
from uringloop.loop import IoUringProactorEventLoopPolicy


@pytest_asyncio.fixture(scope="package", autouse=True)
async def event_loop_policy():
asyncio.set_event_loop_policy(IouringProactorEventLoopPolicy())
@pytest.fixture(scope="package")
def event_loop_policy():
# overriding this fixture is the documented pytest-asyncio way to run
# every test in the package on loops created by this policy
return IoUringProactorEventLoopPolicy()

# TODO: remove pytest_asyncio warning
@pytest.fixture
def event_loop():
loop = asyncio.get_event_loop()
yield cast(IouringProactorEventLoop, loop)
loop.close()

@pytest.fixture
def unix_socket_path():
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/loop/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

import pytest

from uringloop.loop import IouringProactorEventLoop, _IouringWritePipeTransport # type: ignore[reportPrivateUsage]
from uringloop.loop import IoUringProactorEventLoop, _IoUringWritePipeTransport # type: ignore[reportPrivateUsage]


@pytest.mark.asyncio
async def test_subprocess_basic():
event_loop = asyncio.get_running_loop()
assert isinstance(event_loop, IouringProactorEventLoop)
assert isinstance(event_loop, IoUringProactorEventLoop)
# Create a protocol class that tracks completion
class SubprocessProtocol(asyncio.SubprocessProtocol):
def __init__(self, exit_future: asyncio.Future[Any]):
Expand Down Expand Up @@ -37,7 +37,7 @@ def process_exited(self):
@pytest.mark.asyncio
async def test_subprocess_io():
event_loop = asyncio.get_running_loop()
assert isinstance(event_loop, IouringProactorEventLoop)
assert isinstance(event_loop, IoUringProactorEventLoop)
class SubprocessProtocol(asyncio.SubprocessProtocol):
def __init__(self, exit_future: asyncio.Future[Any]):
self.exit_future = exit_future
Expand All @@ -56,7 +56,7 @@ def process_exited(self):

# Write to stdin and close
stdin = transport.get_pipe_transport(0)
assert isinstance(stdin, _IouringWritePipeTransport), f"stdin got unexpected type {type(stdin)}"
assert isinstance(stdin, _IoUringWritePipeTransport), f"stdin got unexpected type {type(stdin)}"
stdin.write(b"test data\n")
stdin.close()

Expand All @@ -70,7 +70,7 @@ def process_exited(self):
@pytest.mark.asyncio
async def test_subprocess_error():
event_loop = asyncio.get_running_loop()
assert isinstance(event_loop, IouringProactorEventLoop)
assert isinstance(event_loop, IoUringProactorEventLoop)
class SubprocessProtocol(asyncio.SubprocessProtocol):
def __init__(self, exit_future: asyncio.Future[Any]):
self.exit_future = exit_future
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/loop/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

import pytest

from uringloop.loop import IouringProactorEventLoop
from uringloop.loop import IoUringProactorEventLoop


@pytest.mark.asyncio
async def test_unix_connection(unix_socket_path: str):
event_loop = asyncio.get_running_loop()
assert isinstance(event_loop, IouringProactorEventLoop)
assert isinstance(event_loop, IoUringProactorEventLoop)
# Server protocol
class EchoServerProtocol(asyncio.Protocol):
def connection_made(self, transport: asyncio.BaseTransport):
Expand Down
8 changes: 4 additions & 4 deletions tests/e2e/proactor/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def server_udp_sock() -> Generator[socket.socket, None, None]:
@pytest_asyncio.fixture
async def init_proactor() -> AsyncGenerator[IoUringProactor, None]:
"""Fixture to create and clean up the proactor and its polling task."""
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
proactor = IoUringProactor()
proactor.set_loop(loop)

Expand All @@ -93,14 +93,14 @@ async def _run_proactor_task():
task = loop.create_task(_run_proactor_task())

def stop_all_coro_if_raise_exception(task: asyncio.Task[None]):
if task.cancelled():
return
if task.exception(): # If task failed
# Cancel all other tasks
# Cancel all other tasks so the test fails instead of hanging
for t in asyncio.all_tasks(loop):
if t != task and not t.done():
t.cancel()

loop.run_until_complete(loop.shutdown_asyncgens())

# Add failure callback
task.add_done_callback(stop_all_coro_if_raise_exception)

Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/proactor/test_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


@pytest.mark.asyncio
async def test_tcp_conmmunication(
async def test_tcp_communication(
init_proactor: IoUringProactor, client_tcp_sock: socket.socket, server_tcp_sock: socket.socket
) -> None:
"""Integration test using real socket communication with the echo server."""
Expand Down
16 changes: 14 additions & 2 deletions uringloop/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
__all__ = ["IoUringProactor", "IouringProactorEventLoopPolicy", "IouringProactorEventLoop"]
__all__ = [
"IoUringProactor",
"IoUringProactorEventLoop",
"IoUringProactorEventLoopPolicy",
# deprecated aliases (0.1.x naming)
"IouringProactorEventLoop",
"IouringProactorEventLoopPolicy",
]


import platform
import re

from uringloop.loop import IouringProactorEventLoop, IouringProactorEventLoopPolicy
from uringloop.loop import (
IoUringProactorEventLoop,
IoUringProactorEventLoopPolicy,
IouringProactorEventLoop,
IouringProactorEventLoopPolicy,
)
from uringloop.proactor import IoUringProactor


Expand Down
17 changes: 11 additions & 6 deletions uringloop/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from uringloop.proactor import IoUringProactor


class _IouringWritePipeTransport(proactor_events._ProactorBaseWritePipeTransport): # type: ignore[reportPrivateUsage]
class _IoUringWritePipeTransport(proactor_events._ProactorBaseWritePipeTransport): # type: ignore[reportPrivateUsage]
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self._read_fut = cast(IoUringProactor, self._loop._proactor).poll_add(self._sock, POLLERR | POLLHUP) # type: ignore[reportPrivateUsage]
Expand All @@ -35,8 +35,8 @@ def _pipe_closed(self, fut: futures.Future[int]):
self.close()


class IouringProactorEventLoop(proactor_events.BaseProactorEventLoop):
"""Linux version of proactor event loop using Iouring."""
class IoUringProactorEventLoop(proactor_events.BaseProactorEventLoop):
"""Linux version of proactor event loop using IoUring."""

def __init__(self, proactor: IoUringProactor | None = None):
# BaseEventLoop.__del__ may run when constructing the default proactor
Expand Down Expand Up @@ -237,7 +237,7 @@ def _child_exited(fut: futures.Future[int]):
fut.add_done_callback(_child_exited)

def _make_write_pipe_transport(self, sock, protocol, waiter=None, extra=None):
return _IouringWritePipeTransport(self, sock, protocol, waiter, extra)
return _IoUringWritePipeTransport(self, sock, protocol, waiter, extra)


# Preserve the child-watcher methods on Python 3.12 and 3.13 by using the Unix
Expand All @@ -248,5 +248,10 @@ def _make_write_pipe_transport(self, sock, protocol, waiter=None, extra=None):
)


class IouringProactorEventLoopPolicy(_BaseDefaultEventLoopPolicy):
_loop_factory = IouringProactorEventLoop
class IoUringProactorEventLoopPolicy(_BaseDefaultEventLoopPolicy):
_loop_factory = IoUringProactorEventLoop


# deprecated aliases kept for backwards compatibility with the 0.1.x naming
IouringProactorEventLoop = IoUringProactorEventLoop
IouringProactorEventLoopPolicy = IoUringProactorEventLoopPolicy
Loading
Loading