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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ wheels/
_liburing.c
_liburing.o
_liburing.*.so
_uringcore.*.so
_uringcore_liburing.*.so
3 changes: 2 additions & 1 deletion docs/phase1-native-ring-decision.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ following work blocks that transition:
native e2e suite against that instrumented archive.
1. Move the selected implementation behind the canonical `_uringcore` name
and replace the comparison-spike module name, stub, tests, and build
configuration.
configuration. This transition was completed after the native NOP request
lifecycle landed.
1. Implement and benchmark the native prepare/submit/reap batch boundary
before making claims about syscall or CPU improvements.
1. Continue with the refcounted multi-completion request state machine only
Expand Down
26 changes: 13 additions & 13 deletions docs/phase1-static-liburing-spike.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Phase 1 static-liburing ring spike

This is the second native ring implementation spike required by Phase 1 of
the roadmap. It mirrors the lifecycle boundary of the raw-syscall
This was the second native ring implementation spike required by Phase 1 of
the roadmap. It mirrored the lifecycle boundary of the raw-syscall
`_uringcore.Ring` with a separate `_uringcore_liburing.Ring` implemented
through the pinned liburing submodule.

Expand All @@ -10,20 +10,20 @@ the module. It therefore has
no runtime dependency on a system `liburing.so`. liburing is used under its
MIT license, whose notice is included in the package.

Like the raw-syscall spike, this module owns ring initialization and teardown
but does not submit or reap operations and is not wired into the Python
At the comparison boundary, the module owned ring initialization and teardown
but did not submit or reap operations and was not wired into the Python
proactor. The build configures and compiles a private archive from the pinned
vendored sources, then links that archive into both the existing CFFI module
and this experimental extension. Source builds therefore do not require a
prebuilt archive or a system `liburing.so`.
and the native extension. Source builds therefore do not require a prebuilt
archive or a system `liburing.so`.

The two lifecycle implementations use the same API and tests. The resulting
[backend decision](phase1-native-ring-decision.md) selects the statically
linked, vendored liburing route. This spike is not yet the production backend:
the follow-up work must add full sanitizer coverage, move this implementation
to `_uringcore`, and replace the comparison-spike module name. The raw spike is
preserved separately on `feature/raw-syscall-ring-spike`; it is not built or
packaged by this branch.
The two lifecycle implementations used the same API and tests. The resulting
[backend decision](phase1-native-ring-decision.md) selected the statically
linked, vendored liburing route. Subsequent Phase 1 work added the native NOP
request lifecycle and promoted this implementation to the canonical
`_uringcore` module name. Full sanitizer coverage and the remaining request
state-machine work are still required. The raw spike is preserved separately
on `feature/raw-syscall-ring-spike`; it is not built or packaged.

On the initial CPython 3.12 x86-64 development build, including debug
information, the module sizes are:
Expand Down
2 changes: 1 addition & 1 deletion scripts/verify_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

EXTENSION_MODULES = (
"uringloop._liburing",
"uringloop._uringcore_liburing",
"uringloop._uringcore",
)


Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
LIBURING_SOURCE = ROOT / "libs"
LIBURING_EXTENSION_NAMES = {
"uringloop._liburing",
"uringloop._uringcore_liburing",
"uringloop._uringcore",
}


Expand Down Expand Up @@ -83,7 +83,7 @@ def _build_vendored_liburing(self):
cmdclass={"build_ext": VendoredLiburingBuildExt},
ext_modules=[
Extension(
"uringloop._uringcore_liburing",
"uringloop._uringcore",
sources=["src/uringcore_liburing.c"],
),
],
Expand Down
14 changes: 7 additions & 7 deletions src/uringcore_liburing.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ PyDoc_STRVAR(

static PyTypeObject UringCoreLiburingRequestType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "uringloop._uringcore_liburing.Request",
.tp_name = "uringloop._uringcore.Request",
.tp_basicsize = sizeof(UringCoreLiburingRequest),
.tp_dealloc = (destructor)uringcore_liburing_request_dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT,
Expand Down Expand Up @@ -657,7 +657,7 @@ PyDoc_STRVAR(

static PyTypeObject UringCoreLiburingRingType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "uringloop._uringcore_liburing.Ring",
.tp_name = "uringloop._uringcore.Ring",
.tp_basicsize = sizeof(UringCoreLiburingRing),
.tp_dealloc = (destructor)uringcore_liburing_ring_dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT,
Expand All @@ -669,15 +669,15 @@ static PyTypeObject UringCoreLiburingRingType = {
.tp_new = uringcore_liburing_ring_new,
};

static PyModuleDef uringcore_liburing_module = {
static PyModuleDef uringcore_module = {
PyModuleDef_HEAD_INIT,
.m_name = "_uringcore_liburing",
.m_doc = "Statically linked liburing ring primitives.",
.m_name = "_uringcore",
.m_doc = "Native ring primitives backed by statically linked liburing.",
.m_size = -1,
};

PyMODINIT_FUNC
PyInit__uringcore_liburing(void)
PyInit__uringcore(void)
{
PyObject *module;

Expand All @@ -688,7 +688,7 @@ PyInit__uringcore_liburing(void)
return NULL;
}

module = PyModule_Create(&uringcore_liburing_module);
module = PyModule_Create(&uringcore_module);
if (module == NULL) {
return NULL;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

import pytest

from uringloop import _uringcore_liburing
from uringloop import _uringcore


def test_static_liburing_core_has_versioned_abi():
assert _uringcore_liburing.ABI_VERSION == 2
def test_native_core_has_versioned_abi():
assert _uringcore.ABI_VERSION == 2
assert _uringcore.Ring.__module__ == "uringloop._uringcore"
assert _uringcore.Request.__module__ == "uringloop._uringcore"


def reap_requests(ring, count):
Expand All @@ -22,13 +24,13 @@ def reap_requests(ring, count):


@pytest.mark.parametrize("entries", [-(2**32), -1, 0, 2**32, 2**64])
def test_static_liburing_ring_rejects_out_of_range_queue_size(entries):
def test_native_ring_rejects_out_of_range_queue_size(entries):
with pytest.raises(ValueError, match="entries must be between"):
_uringcore_liburing.Ring(entries)
_uringcore.Ring(entries)


def test_static_liburing_ring_owns_and_releases_kernel_resources():
ring = _uringcore_liburing.Ring(8)
def test_native_ring_owns_and_releases_kernel_resources():
ring = _uringcore.Ring(8)

assert ring.sq_entries >= 8
assert ring.cq_entries >= ring.sq_entries
Expand All @@ -41,17 +43,17 @@ def test_static_liburing_ring_owns_and_releases_kernel_resources():
assert ring.closed is True


def test_static_liburing_ring_context_manager_closes_resources():
with _uringcore_liburing.Ring(entries=8) as ring:
def test_native_ring_context_manager_closes_resources():
with _uringcore.Ring(entries=8) as ring:
assert ring.closed is False

assert ring.closed is True
with pytest.raises(RuntimeError, match="ring is closed"):
ring.__enter__()


def test_static_liburing_ring_submits_and_reaps_native_nop_requests():
ring = _uringcore_liburing.Ring(8)
def test_native_ring_submits_and_reaps_native_nop_requests():
ring = _uringcore.Ring(8)
requests = [ring.prepare_nop(), ring.prepare_nop()]

assert ring.pending == 2
Expand All @@ -71,8 +73,8 @@ def test_static_liburing_ring_submits_and_reaps_native_nop_requests():
assert [request.flags for request in requests] == [0, 0]


def test_static_liburing_ring_keeps_request_alive_until_completion():
ring = _uringcore_liburing.Ring(8)
def test_native_ring_keeps_request_alive_until_completion():
ring = _uringcore.Ring(8)

ring.prepare_nop()
assert ring.submit() == 1
Expand All @@ -82,8 +84,8 @@ def test_static_liburing_ring_keeps_request_alive_until_completion():
assert request.result == 0


def test_static_liburing_ring_close_releases_prepared_request_ownership():
ring = _uringcore_liburing.Ring(8)
def test_native_ring_close_releases_prepared_request_ownership():
ring = _uringcore.Ring(8)
request = ring.prepare_nop()

ring.close()
Expand All @@ -96,10 +98,10 @@ def test_static_liburing_ring_close_releases_prepared_request_ownership():


@pytest.mark.parametrize("max_completions", [-1, 0, 2**32, 2**64])
def test_static_liburing_ring_reap_rejects_out_of_range_batch_size(
def test_native_ring_reap_rejects_out_of_range_batch_size(
max_completions,
):
ring = _uringcore_liburing.Ring(8)
ring = _uringcore.Ring(8)

with pytest.raises(ValueError, match="max_completions must be between"):
ring.reap(max_completions)
File renamed without changes.
Loading