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
116 changes: 98 additions & 18 deletions ext/io/event/selector/uring.c
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,48 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) {

#pragma mark - Methods

typedef int (*io_uring_queue_init_function)(unsigned entries, struct io_uring *ring, unsigned flags);

static int IO_Event_Selector_URing_queue_init_with(unsigned entries, struct io_uring *ring, unsigned int flags, io_uring_queue_init_function queue_init) {
while (true) {
int result = queue_init(entries, ring, flags);
if (result != -EINVAL) return result;

unsigned int fallback = flags;

// DEFER_TASKRUN and TASKRUN_FLAG form the newest optional optimization
// group and must be removed together to preserve their flag dependencies.
#ifdef IORING_SETUP_DEFER_TASKRUN
fallback &= ~IORING_SETUP_DEFER_TASKRUN;
#endif
#ifdef IORING_SETUP_TASKRUN_FLAG
fallback &= ~IORING_SETUP_TASKRUN_FLAG;
#endif
if (fallback != flags) {
flags = fallback;
continue;
}

#ifdef IORING_SETUP_SINGLE_ISSUER
fallback &= ~IORING_SETUP_SINGLE_ISSUER;
#endif
if (fallback != flags) {
flags = fallback;
continue;
}

#ifdef IORING_SETUP_SUBMIT_ALL
fallback &= ~IORING_SETUP_SUBMIT_ALL;
#endif
if (fallback != flags) {
flags = fallback;
continue;
}

return result;
}
}

VALUE IO_Event_Selector_URing_initialize(VALUE self, VALUE loop) {
struct IO_Event_Selector_URing *selector = NULL;
TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
Expand Down Expand Up @@ -353,16 +395,7 @@ VALUE IO_Event_Selector_URing_initialize(VALUE self, VALUE loop) {
flags |= IORING_SETUP_SUBMIT_ALL;
#endif

int result = io_uring_queue_init(URING_ENTRIES, &selector->ring, flags);

#ifdef IORING_SETUP_SUBMIT_ALL
if (result == -EINVAL) {
// IORING_SETUP_SUBMIT_ALL was added in Linux 5.18; retry without it.
if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_initialize: no IORING_SETUP_SUBMIT_ALL\n");
flags &= ~IORING_SETUP_SUBMIT_ALL;
result = io_uring_queue_init(URING_ENTRIES, &selector->ring, flags);
}
#endif
int result = IO_Event_Selector_URing_queue_init_with(URING_ENTRIES, &selector->ring, flags, io_uring_queue_init);

if (result < 0) {
rb_syserr_fail(-result, "IO_Event_Selector_URing_initialize:io_uring_queue_init");
Expand Down Expand Up @@ -1740,14 +1773,7 @@ static int IO_Event_Selector_URing_supported_p(void) {
#ifdef IORING_SETUP_SUBMIT_ALL
flags |= IORING_SETUP_SUBMIT_ALL;
#endif
int result = io_uring_queue_init(32, &ring, flags);

#ifdef IORING_SETUP_SUBMIT_ALL
if (result == -EINVAL) {
flags &= ~IORING_SETUP_SUBMIT_ALL;
result = io_uring_queue_init(32, &ring, flags);
}
#endif
int result = IO_Event_Selector_URing_queue_init_with(32, &ring, flags, io_uring_queue_init);

if (result < 0) {
rb_warn("io_uring_queue_init() was available at compile time but failed at run time: %s\n", strerror(-result));
Expand All @@ -1760,6 +1786,58 @@ static int IO_Event_Selector_URing_supported_p(void) {
return 1;
}

static unsigned int IO_Event_Selector_URing_test_queue_init_calls = 0;

static int IO_Event_Selector_URing_test_queue_init(unsigned entries, struct io_uring *ring, unsigned flags) {
(void)entries;
(void)ring;

IO_Event_Selector_URing_test_queue_init_calls += 1;

unsigned int unsupported = 0;
#ifdef IORING_SETUP_SINGLE_ISSUER
unsupported |= IORING_SETUP_SINGLE_ISSUER;
#endif
#ifdef IORING_SETUP_DEFER_TASKRUN
unsupported |= IORING_SETUP_DEFER_TASKRUN;
#endif
#ifdef IORING_SETUP_TASKRUN_FLAG
unsupported |= IORING_SETUP_TASKRUN_FLAG;
#endif

return flags & unsupported ? -EINVAL : 0;
}

static VALUE IO_Event_Selector_URing_test_setup_flag_fallback(VALUE self) {
(void)self;

unsigned int flags = 0;
unsigned int unsupported = 0;
#ifdef IORING_SETUP_SINGLE_ISSUER
flags |= IORING_SETUP_SINGLE_ISSUER;
unsupported |= IORING_SETUP_SINGLE_ISSUER;
#endif
#ifdef IORING_SETUP_DEFER_TASKRUN
flags |= IORING_SETUP_DEFER_TASKRUN;
unsupported |= IORING_SETUP_DEFER_TASKRUN;
#endif
#ifdef IORING_SETUP_TASKRUN_FLAG
flags |= IORING_SETUP_TASKRUN_FLAG;
unsupported |= IORING_SETUP_TASKRUN_FLAG;
#endif
#ifdef IORING_SETUP_SUBMIT_ALL
flags |= IORING_SETUP_SUBMIT_ALL;
#endif

if (unsupported == 0) return Qnil;

struct io_uring ring;
IO_Event_Selector_URing_test_queue_init_calls = 0;
int result = IO_Event_Selector_URing_queue_init_with(32, &ring, flags, IO_Event_Selector_URing_test_queue_init);

return rb_ary_new_from_args(2, INT2NUM(result), UINT2NUM(IO_Event_Selector_URing_test_queue_init_calls));
}

void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
if (!IO_Event_Selector_URing_supported_p()) {
return;
Expand Down Expand Up @@ -1803,4 +1881,6 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
rb_define_method(IO_Event_Selector_URing, "io_close", IO_Event_Selector_URing_io_close, 1);

rb_define_method(IO_Event_Selector_URing, "process_wait", IO_Event_Selector_URing_process_wait, 3);

rb_define_singleton_method(IO_Event_Selector_URing, "test_setup_flag_fallback", IO_Event_Selector_URing_test_setup_flag_fallback, 0);
}
20 changes: 20 additions & 0 deletions test/io/event/selector/uring.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

require "io/event"

return unless defined?(IO::Event::Selector::URing)

describe IO::Event::Selector::URing do
it "falls back when newer setup flags are unavailable" do
result = subject.test_setup_flag_fallback
skip "No optional setup flags are available" unless result

status, attempts = result

expect(status).to be == 0
expect(attempts).to be > 1
end
end
Loading