diff --git a/ext/io/event/selector/uring.c b/ext/io/event/selector/uring.c index 7bbaea41..9b3b959a 100644 --- a/ext/io/event/selector/uring.c +++ b/ext/io/event/selector/uring.c @@ -81,6 +81,7 @@ struct IO_Event_Selector_URing_Completion struct IO_Event_List list; struct IO_Event_Selector_URing_Waiting *waiting; + bool operation_pending; bool cancellation_pending; }; @@ -207,10 +208,12 @@ struct IO_Event_Selector_URing_Completion * IO_Event_Selector_URing_Completion_a if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_acquire(%p, limit=%ld)\n", (void*)completion, selector->completions.limit); assert(completion->waiting == NULL); + assert(!completion->operation_pending); assert(!completion->cancellation_pending); waiting->completion = completion; completion->waiting = waiting; + completion->operation_pending = true; return completion; } @@ -230,6 +233,7 @@ inline static void IO_Event_Selector_URing_Completion_recycle(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion) { assert(completion->waiting == NULL); + assert(!completion->operation_pending); assert(!completion->cancellation_pending); IO_Event_List_prepend(&selector->free_list, &completion->list); @@ -240,6 +244,9 @@ void IO_Event_Selector_URing_Completion_complete(struct IO_Event_Selector_URing { if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_complete(%p)\n", (void*)completion); + assert(completion->operation_pending); + completion->operation_pending = false; + IO_Event_Selector_URing_Completion_cancel(completion); // A cancellation SQE still refers to this completion record. Keep it out of @@ -257,7 +264,7 @@ void IO_Event_Selector_URing_Completion_cancellation_complete(struct IO_Event_Se assert(completion->cancellation_pending); completion->cancellation_pending = false; - if (completion->waiting == NULL) { + if (!completion->operation_pending) { IO_Event_Selector_URing_Completion_recycle(selector, completion); } } @@ -283,6 +290,7 @@ void IO_Event_Selector_URing_Completion_initialize(void *element) IO_Event_List_initialize(&completion->list); completion->list.type = &IO_Event_Selector_URing_Completion_Type; completion->waiting = NULL; + completion->operation_pending = false; completion->cancellation_pending = false; } diff --git a/releases.md b/releases.md index 802b7ac2..8b69dcaa 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## Unreleased - Fix the `URing` completion free-list empty check so its sole entry can be reused instead of unnecessarily allocating a new completion. + - Keep cancelled `URing` completion records allocated until both the operation and cancellation completions have been processed. ## v1.20.0 diff --git a/test/io/event/selector/cancellable.rb b/test/io/event/selector/cancellable.rb index 575fe36a..88eceb4e 100644 --- a/test/io/event/selector/cancellable.rb +++ b/test/io/event/selector/cancellable.rb @@ -100,6 +100,44 @@ selector.select(0.1) end end + + it "can reuse completions after cancelling waits" do + skip "Requires the URing selector" unless defined?(IO::Event::Selector::URing) && selector.is_a?(IO::Event::Selector::URing) + + pid = Process.fork do + local_selector = selector.class.new(Fiber.current) + local_input, local_output = IO.pipe + + begin + cancelled_waiter = Fiber.new do + local_selector.io_wait(Fiber.current, local_input, IO::READABLE) + rescue Interrupt + # The pending wait was cancelled. + end + + cancelled_waiter.transfer + cancelled_waiter.raise(Interrupt) + local_selector.select(0.01) + + waiters = 2.times.map do + Fiber.new do + local_selector.io_wait(Fiber.current, local_input, IO::READABLE) + rescue Interrupt + # The pending wait was cancelled. + end + end + + waiters.each(&:transfer) + ensure + local_selector.close + local_input.close + local_output.close + end + end + + _, status = Process.wait2(pid) + expect(status).to be(:success?) + end end end