From c310f2296fdac743e0eefd2c6c79773e06b71d8c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Aug 2026 13:00:05 +1200 Subject: [PATCH 1/3] Add io_uring setup flag fallback regression test Assisted-By: devx/efad4cc8-cf12-4d6e-8c55-6f2c3a6683d2 --- ext/io/event/selector/uring.c | 89 ++++++++++++++++++++++++++------- test/io/event/selector/uring.rb | 20 ++++++++ 2 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 test/io/event/selector/uring.rb diff --git a/ext/io/event/selector/uring.c b/ext/io/event/selector/uring.c index 9b3b959a..1ff37966 100644 --- a/ext/io/event/selector/uring.c +++ b/ext/io/event/selector/uring.c @@ -322,6 +322,21 @@ 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) { + int result = queue_init(entries, ring, flags); + +#ifdef IORING_SETUP_SUBMIT_ALL + if (result == -EINVAL) { + flags &= ~IORING_SETUP_SUBMIT_ALL; + result = queue_init(entries, ring, flags); + } +#endif + + 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); @@ -353,16 +368,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"); @@ -1740,14 +1746,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)); @@ -1760,6 +1759,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; @@ -1803,4 +1854,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); } diff --git a/test/io/event/selector/uring.rb b/test/io/event/selector/uring.rb new file mode 100644 index 00000000..9e7d0c91 --- /dev/null +++ b/test/io/event/selector/uring.rb @@ -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 From b78a303a0aa5762142ef3892506bdb53ac189f69 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Aug 2026 13:00:28 +1200 Subject: [PATCH 2/3] Negotiate optional io_uring setup flags Assisted-By: devx/efad4cc8-cf12-4d6e-8c55-6f2c3a6683d2 --- ext/io/event/selector/uring.c | 39 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/ext/io/event/selector/uring.c b/ext/io/event/selector/uring.c index 1ff37966..4ccfce47 100644 --- a/ext/io/event/selector/uring.c +++ b/ext/io/event/selector/uring.c @@ -325,16 +325,43 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) { 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) { - int result = queue_init(entries, ring, flags); + 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 - if (result == -EINVAL) { - flags &= ~IORING_SETUP_SUBMIT_ALL; - result = queue_init(entries, ring, flags); - } + fallback &= ~IORING_SETUP_SUBMIT_ALL; #endif + if (fallback != flags) { + flags = fallback; + continue; + } - return result; + return result; + } } VALUE IO_Event_Selector_URing_initialize(VALUE self, VALUE loop) { From ffd14d3292da0d97222d766f8f4d13899e4c1ef2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 27 Aug 2026 13:01:17 +1200 Subject: [PATCH 3/3] Fix test formatting Assisted-By: devx/efad4cc8-cf12-4d6e-8c55-6f2c3a6683d2 --- test/io/event/selector/uring.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/io/event/selector/uring.rb b/test/io/event/selector/uring.rb index 9e7d0c91..d517badb 100644 --- a/test/io/event/selector/uring.rb +++ b/test/io/event/selector/uring.rb @@ -11,9 +11,9 @@ 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