Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/liburing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .liburing import * # noqa
from .version import * # noqa

from .uring import * # noqa

__version__ = "2026.3.30"

11 changes: 11 additions & 0 deletions src/liburing/class.zig
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,17 @@ pub const Param = extern struct {

const Self = @This();

pub fn __new__(flags: ?u32) !Self {
const p: *c.io_uring_params = try std.heap.c_allocator.create(c.io_uring_params);
p.* = std.mem.zeroes(c.io_uring_params);
p.flags = flags orelse 0;
return .{ ._io_uring_params = p };
}

pub fn __del__(self: *const Self) void {
if (self._io_uring_params) |p| std.heap.c_allocator.destroy(p);
}

pub fn get_sq_entries(self: *const Self) ?u32 {
if (self._io_uring_params) |p| return p.sq_entries;
return oz.raiseRuntimeError("`Param()` not initialized properly!");
Expand Down
5 changes: 5 additions & 0 deletions src/liburing/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ const oz = @import("PyOZ");

pub const Liburing = oz.module(.{
.name = "liburing",

// `uring.py` wraps it.
.funcs = &.{
oz.func("_io_uring_queue_init_mem", @import("uring.zig")._io_uring_queue_init_mem, null),
},
.from = &.{
oz.withSource(@import("const.zig"), @embedFile("const.zig")),
oz.withSource(@import("class.zig"), @embedFile("class.zig")),
Expand Down
29 changes: 29 additions & 0 deletions src/liburing/uring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'''
Liburing

Python layer on top of PYoZ built functions
'''

from .liburing import _io_uring_queue_init_mem

def io_uring_queue_init_mem(entries, ring, param, buf):
"""Setup `Ring` using caller supplied memory. Returns bytes used from `buf`.

Example
>>> import mmap
>>> ring = Ring()
>>> param = Param()
>>> buf = mmap.mmap(-1, 1024**2 * 2) # page-aligned & zeroed
>>> io_uring_queue_init_mem(8, ring, param, buf)

Note
- `buf` must be page-size aligned and zeroed; `mmap.mmap(-1, size)` satisfies both.
A `bytearray` does not (kernel rejects it with `EINVAL`).
- Hold a reference to `buf` until `io_uring_queue_exit`, else the memory gets
freed while the ring still uses it.
"""
return _io_uring_queue_init_mem(entries, ring, param, memoryview(buf))

__all__ = [
"io_uring_queue_init_mem"
]
11 changes: 7 additions & 4 deletions src/liburing/uring.zig
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ pub fn io_uring_opcode_supported(probe: *Probe, op: i32) bool {
return (c.io_uring_opcode_supported(probe._io_uring_probe, op) == 1);
}

///Warning
/// - Coded but not tested!!!
pub fn io_uring_queue_init_mem(entries: u32, ring: *Ring, p: *Param, buf: ?*anyopaque, buf_size: usize) ?i32 {
return e.trap_error(c.io_uring_queue_init_mem(entries, ring._io_uring, p._io_uring_params, buf, buf_size));
///Internal - wrapped by Python `io_uring_queue_init_mem` (see `uring.py`) which accepts
///the buffer directly and passes a `memoryview` here. Returns bytes used from `buf`.
pub fn _io_uring_queue_init_mem(entries: u32, ring: *Ring, p: *Param, buf: oz.MemoryView) ?i32 {
if (ring._io_uring.ring_fd > 0) return oz.raiseRuntimeError("`io_uring_queue_init_mem(ring)` already initialized!");
const params = p._io_uring_params orelse
return oz.raiseValueError("`io_uring_queue_init_mem` - `Param` not initialized!!");
return e.trap_error(c.io_uring_queue_init_mem(entries, ring._io_uring, params, @ptrCast(@constCast(buf.data.ptr)), buf.data.len));
}

pub fn io_uring_queue_init_params(entries: u32, ring: *Ring, param: *Param) ?i32 {
Expand Down
20 changes: 17 additions & 3 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import getpass
import tempfile
import liburing
import mmap


@pytest.fixture
Expand Down Expand Up @@ -34,11 +35,24 @@ def tmp_dir():


# liburing start >>>
@pytest.fixture
def ring():
@pytest.fixture(params=['queue_init', 'queue_init_mem'])
def ring(request):
'''
=======================
params:
queue_init:
Init queue using `io_uring_queue_init`
queue_init_mem:
Init queue using `io_uring_queue_init_mem`
'''
ring = liburing.Ring()
try:
liburing.io_uring_queue_init(1024, ring)
if request.param == 'queue_init':
liburing.io_uring_queue_init(1024, ring)
else:
buf = mmap.mmap(-1, 8 * 1024 * 1024)
param = liburing.Param()
liburing.io_uring_queue_init_mem(1024, ring, param, buf)
yield ring
finally:
liburing.io_uring_queue_exit(ring)
Expand Down
22 changes: 22 additions & 0 deletions test/queue/init_exit_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import mmap
import pytest
import liburing

Expand Down Expand Up @@ -83,3 +84,24 @@ def test_io_uring_submit_and_wait(ring, cqe):
assert liburing.io_uring_sq_ready(ring) == 0
liburing.io_uring_peek_cqe(ring, cqe)
assert cqe[0].user_data == 123


def test_io_uring_init_mem():
ring = liburing.Ring()
param = liburing.Param()
buf = mmap.mmap(-1, 2 * 1024 * 1024) # page-aligned & zeroed; keep alive until exit
used = liburing.io_uring_queue_init_mem(8, ring, param, buf)

try:
assert used > 0

sqe = liburing.io_uring_get_sqe(ring)
liburing.io_uring_prep_nop(sqe)
sqe.user_data = 7
assert liburing.io_uring_submit(ring) == 1
cqe = liburing.Cqe()
liburing.io_uring_wait_cqe(ring, cqe)
assert cqe[0].user_data == 7
liburing.io_uring_cqe_seen(ring, cqe[0])
finally:
liburing.io_uring_queue_exit(ring)