Skip to content

Commit d995558

Browse files
Add URing io_wait unmatched poll repro
1 parent c560cde commit d995558

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

ext/io/event/selector/uring.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,16 @@ int events_from_poll_flags(short flags) {
581581
return events;
582582
}
583583

584+
#ifdef RUBY_DEBUG
585+
VALUE IO_Event_Selector_URing_test_io_wait_result(VALUE self, VALUE _result, VALUE _events) {
586+
int result = NUM2INT(_result);
587+
int events = NUM2INT(_events);
588+
short flags = poll_flags_from_events(events);
589+
590+
return RB_INT2NUM(events_from_poll_flags(result & flags));
591+
}
592+
#endif
593+
584594
struct io_wait_arguments {
585595
struct IO_Event_Selector_URing *selector;
586596
struct IO_Event_Selector_URing_Waiting *waiting;
@@ -1373,6 +1383,10 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
13731383

13741384
rb_define_method(IO_Event_Selector_URing, "io_wait", IO_Event_Selector_URing_io_wait, 3);
13751385

1386+
#ifdef RUBY_DEBUG
1387+
rb_define_singleton_method(IO_Event_Selector_URing, "__test_io_wait_result", IO_Event_Selector_URing_test_io_wait_result, 2);
1388+
#endif
1389+
13761390
#ifdef HAVE_RUBY_IO_BUFFER_H
13771391
rb_define_method(IO_Event_Selector_URing, "io_read", IO_Event_Selector_URing_io_read_compatible, -1);
13781392
rb_define_method(IO_Event_Selector_URing, "io_write", IO_Event_Selector_URing_io_write_compatible, -1);

test/io/event/selector/uring.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
require "io/event"
7+
require "io/event/selector"
8+
9+
describe IO::Event::Selector do
10+
with "URing" do
11+
it "does not expose unmatched poll completions as integer zero from io_wait" do
12+
skip "URing is not available" unless subject.const_defined?(:URing)
13+
14+
uring = subject.const_get(:URing)
15+
skip "URing debug repro hook is not available" unless uring.respond_to?(:__test_io_wait_result)
16+
17+
# io_uring poll completions can include flags we did not request. The
18+
# current implementation filters the raw poll result before translating
19+
# it to Ruby IO events, which can produce Integer(0). Ruby's socket
20+
# connect path treats any non-false, non-negative result as success.
21+
result = uring.__test_io_wait_result(IO::PRIORITY, IO::READABLE | IO::WRITABLE)
22+
23+
expect(result).not.to be == 0
24+
end
25+
end
26+
end

0 commit comments

Comments
 (0)