diff --git a/ext/io/event/selector/uring.c b/ext/io/event/selector/uring.c index 9b3b959a..6dd87542 100644 --- a/ext/io/event/selector/uring.c +++ b/ext/io/event/selector/uring.c @@ -495,15 +495,18 @@ void IO_Event_Selector_URing_dump_completion_queue(struct IO_Event_Selector_URin } // Flush the submission queue, optionally yielding if unsuccessful. +typedef int (*io_uring_submit_function)(struct io_uring *ring); + static -int io_uring_submit_all(struct IO_Event_Selector_URing *selector, bool yield) { +int io_uring_submit_all_with(struct IO_Event_Selector_URing *selector, bool yield, io_uring_submit_function submit) { struct io_uring *ring = &selector->ring; while (io_uring_sq_ready(ring) > 0) { - int result = io_uring_submit(&selector->ring); + int result = submit(&selector->ring); if (result == -EBUSY || result == -EAGAIN) { if (yield) IO_Event_Selector_yield(&selector->backend); + return result; } else if (result < 0) { rb_syserr_fail(-result, "io_uring_submit_all:io_uring_submit"); return result; @@ -514,6 +517,11 @@ int io_uring_submit_all(struct IO_Event_Selector_URing *selector, bool yield) { return 0; } +static +int io_uring_submit_all(struct IO_Event_Selector_URing *selector, bool yield) { + return io_uring_submit_all_with(selector, yield, io_uring_submit); +} + // Flush the submission queue if pending operations are present. static int io_uring_submit_flush(struct IO_Event_Selector_URing *selector) { @@ -1760,6 +1768,32 @@ static int IO_Event_Selector_URing_supported_p(void) { return 1; } +static int IO_Event_Selector_URing_test_submission_count = 0; + +static int IO_Event_Selector_URing_test_submission_backpressure(struct io_uring *ring) { + (void)ring; + + if (++IO_Event_Selector_URing_test_submission_count > 1) { + rb_raise(rb_eRuntimeError, "submission was retried before processing completions"); + } + + return -EAGAIN; +} + +static VALUE IO_Event_Selector_URing_test_submission_backpressure_p(VALUE self) { + struct IO_Event_Selector_URing *selector = NULL; + TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector); + + if (!io_uring_get_sqe(&selector->ring)) { + rb_raise(rb_eRuntimeError, "could not prepare test submission"); + } + + IO_Event_Selector_URing_test_submission_count = 0; + int result = io_uring_submit_all_with(selector, false, IO_Event_Selector_URing_test_submission_backpressure); + + return rb_ary_new_from_args(2, INT2NUM(result), INT2NUM(IO_Event_Selector_URing_test_submission_count)); +} + void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) { if (!IO_Event_Selector_URing_supported_p()) { return; @@ -1803,4 +1837,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_private_method(IO_Event_Selector_URing, "test_submission_backpressure", IO_Event_Selector_URing_test_submission_backpressure_p, 0); } diff --git a/test/io/event/selector/uring.rb b/test/io/event/selector/uring.rb new file mode 100644 index 00000000..bb12ed4c --- /dev/null +++ b/test/io/event/selector/uring.rb @@ -0,0 +1,23 @@ +# 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 + let(:selector) {subject.new(Fiber.current)} + + after do + selector&.close + end + + it "returns control when submission encounters backpressure" do + result, submissions = selector.send(:test_submission_backpressure) + + expect(result).to be == -Errno::EAGAIN::Errno + expect(submissions).to be == 1 + end +end