io-event 1.16.2 → 1.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,13 +11,21 @@
11
11
  #include <stdbool.h>
12
12
  #include <stdint.h>
13
13
  #include <time.h>
14
+ #include <unistd.h>
14
15
 
15
16
  #include "../interrupt.h"
16
17
 
17
- #include "pidfd.c"
18
-
19
18
  #include <linux/version.h>
20
19
 
20
+ // `io_uring` support for `IORING_OP_WAITID` was introduced in Linux 6.7. When available, we use it to wait for process exit directly in the ring, instead of polling on a pidfd.
21
+ #if defined(HAVE_IO_URING_PREP_WAITID) && (LINUX_VERSION_CODE >= KERNEL_VERSION(6,7,0))
22
+ #define IO_EVENT_SELECTOR_URING_USE_WAITID
23
+ #endif
24
+
25
+ #ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
26
+ #include "pidfd.c"
27
+ #endif
28
+
21
29
  enum {
22
30
  DEBUG = 0,
23
31
  DEBUG_COMPLETION = 0,
@@ -32,10 +40,7 @@ struct IO_Event_Selector_URing
32
40
  {
33
41
  struct IO_Event_Selector backend;
34
42
  struct io_uring ring;
35
-
36
- // Flag indicating whether the selector is currently blocked in a system call.
37
- // Set to 1 when blocked in io_uring_wait_cqe_timeout() without GVL, 0 otherwise.
38
- int blocked;
43
+ pid_t owner;
39
44
 
40
45
  // Interrupt used to wake the selector from another thread without touching the ring's SQ.
41
46
  // This allows IORING_SETUP_SINGLE_ISSUER: only the owner thread ever submits SQEs.
@@ -76,8 +81,27 @@ struct IO_Event_Selector_URing_Completion
76
81
  struct IO_Event_List list;
77
82
 
78
83
  struct IO_Event_Selector_URing_Waiting *waiting;
84
+ bool operation_pending;
85
+ bool cancellation_pending;
79
86
  };
80
87
 
88
+ // Cancellation completions use the low bit to distinguish them from the
89
+ // completion of the operation being cancelled. Completion records are aligned,
90
+ // so the low bit is otherwise always clear.
91
+ #define IO_EVENT_SELECTOR_URING_CANCELLATION_TAG ((uint64_t)1)
92
+
93
+ static inline uint64_t
94
+ IO_Event_Selector_URing_Completion_cancellation_data(struct IO_Event_Selector_URing_Completion *completion)
95
+ {
96
+ return ((uint64_t)(uintptr_t)completion) | IO_EVENT_SELECTOR_URING_CANCELLATION_TAG;
97
+ }
98
+
99
+ static inline struct IO_Event_Selector_URing_Completion *
100
+ IO_Event_Selector_URing_Completion_from_cancellation_data(uint64_t data)
101
+ {
102
+ return (struct IO_Event_Selector_URing_Completion *)(uintptr_t)(data & ~IO_EVENT_SELECTOR_URING_CANCELLATION_TAG);
103
+ }
104
+
81
105
  static
82
106
  void IO_Event_Selector_URing_Completion_mark(void *_completion)
83
107
  {
@@ -115,14 +139,20 @@ void IO_Event_Selector_URing_Type_compact(void *_selector)
115
139
  static
116
140
  void close_internal(struct IO_Event_Selector_URing *selector)
117
141
  {
118
- if (selector->interrupt.descriptor >= 0) {
119
- IO_Event_Interrupt_close(&selector->interrupt);
142
+ if (selector->owner == getpid()) {
143
+ if (selector->interrupt.descriptor >= 0) {
144
+ IO_Event_Interrupt_close(&selector->interrupt);
145
+ selector->interrupt.descriptor = -1;
146
+ selector->wakeup_registered = 0;
147
+ }
148
+
149
+ if (selector->ring.ring_fd >= 0) {
150
+ io_uring_queue_exit(&selector->ring);
151
+ selector->ring.ring_fd = -1;
152
+ }
153
+ } else {
120
154
  selector->interrupt.descriptor = -1;
121
155
  selector->wakeup_registered = 0;
122
- }
123
-
124
- if (selector->ring.ring_fd >= 0) {
125
- io_uring_queue_exit(&selector->ring);
126
156
  selector->ring.ring_fd = -1;
127
157
  }
128
158
  }
@@ -177,8 +207,13 @@ struct IO_Event_Selector_URing_Completion * IO_Event_Selector_URing_Completion_a
177
207
 
178
208
  if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_acquire(%p, limit=%ld)\n", (void*)completion, selector->completions.limit);
179
209
 
210
+ assert(completion->waiting == NULL);
211
+ assert(!completion->operation_pending);
212
+ assert(!completion->cancellation_pending);
213
+
180
214
  waiting->completion = completion;
181
215
  completion->waiting = waiting;
216
+ completion->operation_pending = true;
182
217
 
183
218
  return completion;
184
219
  }
@@ -195,14 +230,45 @@ void IO_Event_Selector_URing_Completion_cancel(struct IO_Event_Selector_URing_Co
195
230
  }
196
231
 
197
232
  inline static
198
- void IO_Event_Selector_URing_Completion_release(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
233
+ void IO_Event_Selector_URing_Completion_recycle(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
199
234
  {
200
- if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_release(%p)\n", (void*)completion);
235
+ assert(completion->waiting == NULL);
236
+ assert(!completion->operation_pending);
237
+ assert(!completion->cancellation_pending);
201
238
 
202
- IO_Event_Selector_URing_Completion_cancel(completion);
203
239
  IO_Event_List_prepend(&selector->free_list, &completion->list);
204
240
  }
205
241
 
242
+ inline static
243
+ void IO_Event_Selector_URing_Completion_complete(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
244
+ {
245
+ if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_complete(%p)\n", (void*)completion);
246
+
247
+ assert(completion->operation_pending);
248
+ completion->operation_pending = false;
249
+
250
+ IO_Event_Selector_URing_Completion_cancel(completion);
251
+
252
+ // A cancellation SQE still refers to this completion record. Keep it out of
253
+ // the free list until that CQE has also been observed, avoiding ABA reuse.
254
+ if (!completion->cancellation_pending) {
255
+ IO_Event_Selector_URing_Completion_recycle(selector, completion);
256
+ }
257
+ }
258
+
259
+ inline static
260
+ void IO_Event_Selector_URing_Completion_cancellation_complete(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
261
+ {
262
+ if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_cancellation_complete(%p)\n", (void*)completion);
263
+
264
+ assert(completion->cancellation_pending);
265
+ completion->cancellation_pending = false;
266
+
267
+ if (!completion->operation_pending) {
268
+ IO_Event_Selector_URing_Completion_recycle(selector, completion);
269
+ }
270
+ }
271
+
206
272
  inline static
207
273
  void IO_Event_Selector_URing_Waiting_cancel(struct IO_Event_Selector_URing_Waiting *waiting)
208
274
  {
@@ -223,6 +289,9 @@ void IO_Event_Selector_URing_Completion_initialize(void *element)
223
289
  struct IO_Event_Selector_URing_Completion *completion = element;
224
290
  IO_Event_List_initialize(&completion->list);
225
291
  completion->list.type = &IO_Event_Selector_URing_Completion_Type;
292
+ completion->waiting = NULL;
293
+ completion->operation_pending = false;
294
+ completion->cancellation_pending = false;
226
295
  }
227
296
 
228
297
  void IO_Event_Selector_URing_Completion_free(void *element)
@@ -237,8 +306,8 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) {
237
306
 
238
307
  IO_Event_Selector_initialize(&selector->backend, self, Qnil);
239
308
  selector->ring.ring_fd = -1;
309
+ selector->owner = 0;
240
310
 
241
- selector->blocked = 0;
242
311
  selector->interrupt.descriptor = -1;
243
312
  selector->wakeup_registered = 0;
244
313
 
@@ -299,6 +368,8 @@ VALUE IO_Event_Selector_URing_initialize(VALUE self, VALUE loop) {
299
368
  rb_syserr_fail(-result, "IO_Event_Selector_URing_initialize:io_uring_queue_init");
300
369
  }
301
370
 
371
+ selector->owner = getpid();
372
+
302
373
  rb_update_max_fd(selector->ring.ring_fd);
303
374
 
304
375
  // Interrupt for cross-thread wakeup: another thread calls signal(); the owner
@@ -339,6 +410,13 @@ VALUE IO_Event_Selector_URing_close(VALUE self) {
339
410
  return Qnil;
340
411
  }
341
412
 
413
+ VALUE IO_Event_Selector_URing_closed_p(VALUE self) {
414
+ struct IO_Event_Selector_URing *selector = NULL;
415
+ TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
416
+
417
+ return selector->ring.ring_fd < 0 || selector->owner != getpid() ? Qtrue : Qfalse;
418
+ }
419
+
342
420
  VALUE IO_Event_Selector_URing_transfer(VALUE self)
343
421
  {
344
422
  struct IO_Event_Selector_URing *selector = NULL;
@@ -480,37 +558,185 @@ struct io_uring_sqe * io_get_sqe(struct IO_Event_Selector_URing *selector) {
480
558
  return sqe;
481
559
  }
482
560
 
561
+ static
562
+ void IO_Event_Selector_URing_Completion_cancel_async(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
563
+ {
564
+ if (completion->cancellation_pending) return;
565
+
566
+ completion->cancellation_pending = true;
567
+
568
+ struct io_uring_sqe *sqe = io_get_sqe(selector);
569
+ io_uring_prep_cancel(sqe, completion, 0);
570
+ io_uring_sqe_set_data64(sqe, IO_Event_Selector_URing_Completion_cancellation_data(completion));
571
+ io_uring_submit_pending(selector);
572
+ }
573
+
574
+ static
575
+ VALUE IO_Event_Selector_URing_Waiting_wait_yield(VALUE _selector)
576
+ {
577
+ struct IO_Event_Selector_URing *selector = (struct IO_Event_Selector_URing *)_selector;
578
+
579
+ return IO_Event_Selector_loop_yield(&selector->backend);
580
+ }
581
+
582
+ static
583
+ void IO_Event_Selector_URing_Waiting_cancel_and_wait(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Waiting *waiting)
584
+ {
585
+ int state = 0;
586
+ VALUE error = Qnil;
587
+
588
+ if (waiting->completion) {
589
+ IO_Event_Selector_URing_Completion_cancel_async(selector, waiting->completion);
590
+
591
+ // The kernel may still be reading from or writing to the supplied buffer.
592
+ // Keep the C frame, buffer lock and completion record alive until the
593
+ // original operation CQE confirms that it can no longer access memory.
594
+ while (waiting->completion) {
595
+ int current_state = 0;
596
+ rb_protect(IO_Event_Selector_URing_Waiting_wait_yield, (VALUE)selector, &current_state);
597
+
598
+ if (current_state && !state) {
599
+ state = current_state;
600
+ error = rb_errinfo();
601
+ }
602
+
603
+ rb_set_errinfo(Qnil);
604
+ }
605
+ }
606
+
607
+ IO_Event_Selector_URing_Waiting_cancel(waiting);
608
+
609
+ if (state) {
610
+ rb_set_errinfo(error);
611
+ rb_jump_tag(state);
612
+ }
613
+ }
614
+
483
615
  #pragma mark - Process.wait
484
616
 
617
+ #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
618
+ // Translate a Ruby/`waitpid`-style pid into the `waitid(2)` idtype and id, mirroring the semantics of `waitpid(2)`:
619
+ //
620
+ // pid == -1 -> any child (P_ALL)
621
+ // pid == 0 -> any child in the caller's process group (P_PGID, id 0; Linux >= 5.4)
622
+ // pid < -1 -> any child in process group |pid| (P_PGID)
623
+ // pid > 0 -> the specific child (P_PID)
624
+ //
625
+ static inline idtype_t process_waitid_type(pid_t pid, id_t *id) {
626
+ if (pid == -1) {
627
+ *id = 0;
628
+ return P_ALL;
629
+ } else if (pid == 0) {
630
+ *id = 0;
631
+ return P_PGID;
632
+ } else if (pid < -1) {
633
+ *id = (id_t)(-pid);
634
+ return P_PGID;
635
+ } else {
636
+ *id = (id_t)pid;
637
+ return P_PID;
638
+ }
639
+ }
640
+ #endif
641
+
485
642
  struct process_wait_arguments {
486
643
  struct IO_Event_Selector_URing *selector;
487
644
  struct IO_Event_Selector_URing_Waiting *waiting;
488
645
 
489
646
  pid_t pid;
490
647
  int flags;
648
+
649
+ #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
650
+ siginfo_t siginfo;
651
+ #else
491
652
  int descriptor;
653
+ #endif
492
654
  };
493
655
 
656
+ #if defined(IO_EVENT_SELECTOR_URING_USE_WAITID) && defined(HAVE_RB_PROCESS_STATUS_FOR)
657
+ static inline int process_wait_status_exited(int exit_status) {
658
+ return (exit_status & 0xff) << 8;
659
+ }
660
+
661
+ static inline int process_wait_status_signaled(int term_signal) {
662
+ return term_signal & 0x7f;
663
+ }
664
+
665
+ static inline int process_wait_status_from_siginfo(const siginfo_t *siginfo) {
666
+ switch (siginfo->si_code) {
667
+ case CLD_EXITED:
668
+ return process_wait_status_exited(siginfo->si_status);
669
+ case CLD_KILLED:
670
+ return process_wait_status_signaled(siginfo->si_status);
671
+ case CLD_DUMPED:
672
+ return process_wait_status_signaled(siginfo->si_status) | 0x80;
673
+ default:
674
+ return 0;
675
+ }
676
+ }
677
+ #endif
678
+
494
679
  static
495
680
  VALUE process_wait_transfer(VALUE _arguments) {
496
681
  struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
497
682
 
683
+ #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
684
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
685
+
686
+ if (arguments->waiting->completion) {
687
+ // The kernel may still write to siginfo or reap the child. Keep the C frame alive until the original operation completes, then distinguish cancellation from process completion:
688
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(arguments->selector, arguments->waiting);
689
+ }
690
+
691
+ int32_t result = arguments->waiting->result;
692
+ if (result == -ECANCELED) {
693
+ return Qfalse;
694
+ }
695
+
696
+ if (DEBUG) fprintf(stderr, "waitid result=%d pid=%d code=%d status=%d\n", result, arguments->siginfo.si_pid, arguments->siginfo.si_code, arguments->siginfo.si_status);
697
+
698
+ if (result < 0) {
699
+ // The `waitid` failed (e.g. `ECHILD` when there are no children). Reproduce the failure as a `Process::Status` carrying the error, rather than raising, so callers like `Process.waitall` / `Process.detach` (which expect `waitpid` to report the error, not raise) behave correctly:
700
+ #ifdef HAVE_RB_PROCESS_STATUS_FOR
701
+ return rb_process_status_for(-1, 0, -result);
702
+ #else
703
+ return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
704
+ #endif
705
+ }
706
+
707
+ #ifdef HAVE_RB_PROCESS_STATUS_FOR
708
+ // The `waitid` operation already reaped the child. Convert the `siginfo_t` result directly into the Ruby process status value:
709
+ return rb_process_status_for(arguments->siginfo.si_pid, process_wait_status_from_siginfo(&arguments->siginfo), 0);
710
+ #else
711
+ // We waited with `WNOWAIT`, so the child has not been reaped yet. `si_pid` tells us exactly which child changed state (important when waiting for any child, e.g. pid -1). Reap it to obtain a correct `Process::Status`:
712
+ return IO_Event_Selector_process_status_reap(arguments->siginfo.si_pid, arguments->flags);
713
+ #endif
714
+ #else
498
715
  IO_Event_Selector_loop_yield(&arguments->selector->backend);
499
716
 
500
717
  if (arguments->waiting->result) {
501
- return IO_Event_Selector_process_status_wait(arguments->pid, arguments->flags);
718
+ return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
502
719
  } else {
503
720
  return Qfalse;
504
721
  }
722
+ #endif
505
723
  }
506
724
 
507
725
  static
508
726
  VALUE process_wait_ensure(VALUE _arguments) {
509
727
  struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
510
728
 
511
- close(arguments->descriptor);
729
+ #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
730
+ // `waitid` may write to stack-backed siginfo while cancellation is pending:
731
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(arguments->selector, arguments->waiting);
732
+ #else
733
+ if (arguments->waiting->completion) {
734
+ IO_Event_Selector_URing_Completion_cancel_async(arguments->selector, arguments->waiting->completion);
735
+ }
512
736
 
737
+ close(arguments->descriptor);
513
738
  IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
739
+ #endif
514
740
 
515
741
  return Qnil;
516
742
  }
@@ -522,11 +748,18 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
522
748
  pid_t pid = NUM2PIDT(_pid);
523
749
  int flags = NUM2INT(_flags);
524
750
 
751
+ #ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
752
+ // `pidfd_open` can only refer to a specific process, so waiting for any child or a process group (pid <= 0) is delegated to the threaded fallback:
753
+ if (pid <= 0) {
754
+ return IO_Event_Selector_process_wait(pid, flags);
755
+ }
756
+
525
757
  int descriptor = pidfd_open(pid, 0);
526
758
  if (descriptor < 0) {
527
759
  rb_syserr_fail(errno, "IO_Event_Selector_URing_process_wait:pidfd_open");
528
760
  }
529
761
  rb_update_max_fd(descriptor);
762
+ #endif
530
763
 
531
764
  struct IO_Event_Selector_URing_Waiting waiting = {
532
765
  .fiber = fiber,
@@ -541,12 +774,30 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
541
774
  .waiting = &waiting,
542
775
  .pid = pid,
543
776
  .flags = flags,
777
+ #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
778
+ .siginfo = {0},
779
+ #else
544
780
  .descriptor = descriptor,
781
+ #endif
545
782
  };
546
783
 
547
- if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
548
784
  struct io_uring_sqe *sqe = io_get_sqe(selector);
785
+
786
+ #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
787
+ id_t id;
788
+ idtype_t idtype = process_waitid_type(pid, &id);
789
+ if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_waitid(fiber=%p, idtype=%d, id=%d, flags=%d)\n", (void*)fiber, idtype, (int)id, flags);
790
+ #ifdef HAVE_RB_PROCESS_STATUS_FOR
791
+ // Reap the child directly; the completion contains enough information to construct the Ruby process status value:
792
+ io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED, 0);
793
+ #else
794
+ // `WNOWAIT` leaves the child in a waitable state so we can reap it with `rb_process_status_wait` afterwards and build a correct `Process::Status`:
795
+ io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED | WNOWAIT, 0);
796
+ #endif
797
+ #else
798
+ if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
549
799
  io_uring_prep_poll_add(sqe, descriptor, POLLIN|POLLHUP|POLLERR);
800
+ #endif
550
801
  io_uring_sqe_set_data(sqe, completion);
551
802
  io_uring_submit_pending(selector);
552
803
 
@@ -591,13 +842,8 @@ static
591
842
  VALUE io_wait_ensure(VALUE _arguments) {
592
843
  struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
593
844
 
594
- // If the operation is still in progress, cancel it:
595
845
  if (arguments->waiting->completion) {
596
- if (DEBUG) fprintf(stderr, "io_wait_ensure:io_uring_prep_cancel(waiting=%p, completion=%p)\n", (void*)arguments->waiting, (void*)arguments->waiting->completion);
597
- struct io_uring_sqe *sqe = io_get_sqe(arguments->selector);
598
- io_uring_prep_cancel(sqe, (void*)arguments->waiting->completion, 0);
599
- io_uring_sqe_set_data(sqe, NULL);
600
- io_uring_submit_now(arguments->selector);
846
+ IO_Event_Selector_URing_Completion_cancel_async(arguments->selector, arguments->waiting->completion);
601
847
  }
602
848
 
603
849
  IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
@@ -701,7 +947,7 @@ io_read_submit(VALUE _arguments)
701
947
  struct io_uring_sqe *sqe = io_get_sqe(selector);
702
948
  io_uring_prep_read(sqe, arguments->descriptor, arguments->buffer, arguments->length, arguments->offset);
703
949
  io_uring_sqe_set_data(sqe, arguments->waiting->completion);
704
- io_uring_submit_now(selector);
950
+ io_uring_submit_pending(selector);
705
951
 
706
952
  IO_Event_Selector_loop_yield(&selector->backend);
707
953
 
@@ -714,16 +960,7 @@ io_read_ensure(VALUE _arguments)
714
960
  struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments;
715
961
  struct IO_Event_Selector_URing *selector = arguments->selector;
716
962
 
717
- // If the operation is still in progress, cancel it:
718
- if (arguments->waiting->completion) {
719
- if (DEBUG) fprintf(stderr, "io_read_ensure:io_uring_prep_cancel(waiting=%p, completion=%p)\n", (void*)arguments->waiting, (void*)arguments->waiting->completion);
720
- struct io_uring_sqe *sqe = io_get_sqe(selector);
721
- io_uring_prep_cancel(sqe, (void*)arguments->waiting->completion, 0);
722
- io_uring_sqe_set_data(sqe, NULL);
723
- io_uring_submit_now(selector);
724
- }
725
-
726
- IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
963
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(selector, arguments->waiting);
727
964
 
728
965
  return Qnil;
729
966
  }
@@ -753,26 +990,97 @@ io_read(struct IO_Event_Selector_URing *selector, VALUE fiber, int descriptor, c
753
990
  );
754
991
  }
755
992
 
756
- VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) {
993
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
994
+ struct io_read_locked_arguments {
995
+ struct IO_Event_Selector_URing *selector;
996
+ VALUE fiber;
997
+ VALUE io;
998
+ off_t from;
999
+ size_t offset;
1000
+ size_t length;
1001
+ bool positional;
1002
+ };
1003
+
1004
+ static VALUE
1005
+ io_read_locked(void *base, size_t size, VALUE _arguments)
1006
+ {
1007
+ struct io_read_locked_arguments *arguments = (struct io_read_locked_arguments *)_arguments;
1008
+
1009
+ if (!IO_Event_Selector_valid_buffer_range(size, arguments->offset, arguments->length)) {
1010
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
1011
+ } else if (arguments->length == 0) {
1012
+ return rb_fiber_scheduler_io_result(0, 0);
1013
+ }
1014
+
1015
+ int descriptor = IO_Event_Selector_io_descriptor(arguments->io);
1016
+ char *buffer = (char*)base + arguments->offset;
1017
+
1018
+ // Avoid the submission and suspension overhead when the operation can
1019
+ // complete immediately. The descriptor must be non-blocking for this
1020
+ // optimistic syscall; if it would block, restore its original mode and
1021
+ // submit the operation to io_uring instead.
1022
+ int flags = IO_Event_Selector_nonblock_set(descriptor);
1023
+ ssize_t result;
1024
+
1025
+ if (arguments->positional) {
1026
+ result = pread(descriptor, buffer, arguments->length, arguments->from);
1027
+ } else {
1028
+ result = read(descriptor, buffer, arguments->length);
1029
+ }
1030
+
1031
+ int error = errno;
1032
+ IO_Event_Selector_nonblock_restore(descriptor, flags);
1033
+
1034
+ if (result >= 0) {
1035
+ return rb_fiber_scheduler_io_result(result, 0);
1036
+ } else if (!IO_Event_try_again(error)) {
1037
+ return rb_fiber_scheduler_io_result(-1, error);
1038
+ }
1039
+
1040
+ off_t from = arguments->positional ? arguments->from : io_seekable(descriptor);
1041
+
1042
+ int completion = io_read(arguments->selector, arguments->fiber, descriptor, buffer, arguments->length, from);
1043
+ if (completion < 0) {
1044
+ return rb_fiber_scheduler_io_result(-1, -completion);
1045
+ }
1046
+
1047
+ return rb_fiber_scheduler_io_result(completion, 0);
1048
+ }
1049
+ #endif
1050
+
1051
+ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _first, VALUE _second) {
757
1052
  struct IO_Event_Selector_URing *selector = NULL;
758
1053
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
759
1054
 
760
- int descriptor = IO_Event_Selector_io_descriptor(io);
1055
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1056
+ struct io_read_locked_arguments arguments = {
1057
+ .selector = selector,
1058
+ .fiber = fiber,
1059
+ .io = io,
1060
+ .offset = NUM2SIZET(_first),
1061
+ .length = NUM2SIZET(_second),
1062
+ .positional = false,
1063
+ };
761
1064
 
1065
+ return rb_io_buffer_locked_for_writing(buffer, io_read_locked, (VALUE)&arguments);
1066
+ #else
762
1067
  void *base;
763
1068
  size_t size;
764
1069
  rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
765
1070
 
766
- size_t length = NUM2SIZET(_length);
767
- size_t offset = NUM2SIZET(_offset);
1071
+ size_t length = NUM2SIZET(_first);
1072
+ size_t offset = NUM2SIZET(_second);
768
1073
  size_t total = 0;
769
- off_t from = io_seekable(descriptor);
770
1074
 
771
1075
  // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
772
1076
  if (offset > size) {
773
1077
  return rb_fiber_scheduler_io_result(-1, EINVAL);
1078
+ } else if (offset == size) {
1079
+ return rb_fiber_scheduler_io_result(0, 0);
774
1080
  }
775
1081
 
1082
+ int descriptor = IO_Event_Selector_io_descriptor(io);
1083
+ off_t from = io_seekable(descriptor);
776
1084
  size_t maximum_size = size - offset;
777
1085
 
778
1086
  // Are we performing a non-blocking read?
@@ -808,8 +1116,10 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
808
1116
  }
809
1117
 
810
1118
  return rb_fiber_scheduler_io_result(total, 0);
1119
+ #endif
811
1120
  }
812
1121
 
1122
+ #if RUBY_FIBER_SCHEDULER_VERSION < 4
813
1123
  static VALUE IO_Event_Selector_URing_io_read_compatible(int argc, VALUE *argv, VALUE self)
814
1124
  {
815
1125
  rb_check_arity(argc, 4, 5);
@@ -822,27 +1132,41 @@ static VALUE IO_Event_Selector_URing_io_read_compatible(int argc, VALUE *argv, V
822
1132
 
823
1133
  return IO_Event_Selector_URing_io_read(self, argv[0], argv[1], argv[2], argv[3], _offset);
824
1134
  }
1135
+ #endif
825
1136
 
826
- VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _length, VALUE _offset) {
1137
+ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _first, VALUE _second) {
827
1138
  struct IO_Event_Selector_URing *selector = NULL;
828
1139
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
829
1140
 
830
- int descriptor = IO_Event_Selector_io_descriptor(io);
1141
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1142
+ struct io_read_locked_arguments arguments = {
1143
+ .selector = selector,
1144
+ .fiber = fiber,
1145
+ .io = io,
1146
+ .from = NUM2OFFT(_from),
1147
+ .offset = NUM2SIZET(_first),
1148
+ .length = NUM2SIZET(_second),
1149
+ .positional = true,
1150
+ };
831
1151
 
1152
+ return rb_io_buffer_locked_for_writing(buffer, io_read_locked, (VALUE)&arguments);
1153
+ #else
832
1154
  void *base;
833
1155
  size_t size;
834
1156
  rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
835
1157
 
836
- size_t length = NUM2SIZET(_length);
837
- size_t offset = NUM2SIZET(_offset);
1158
+ size_t length = NUM2SIZET(_first);
1159
+ size_t offset = NUM2SIZET(_second);
838
1160
  size_t total = 0;
839
- off_t from = NUM2OFFT(_from);
840
1161
 
841
1162
  // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
842
1163
  if (offset > size) {
843
1164
  return rb_fiber_scheduler_io_result(-1, EINVAL);
1165
+ } else if (offset == size) {
1166
+ return rb_fiber_scheduler_io_result(0, 0);
844
1167
  }
845
-
1168
+ off_t from = NUM2OFFT(_from);
1169
+ int descriptor = IO_Event_Selector_io_descriptor(io);
846
1170
  size_t maximum_size = size - offset;
847
1171
  while (maximum_size) {
848
1172
  int result = io_read(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -865,6 +1189,7 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE
865
1189
  }
866
1190
 
867
1191
  return rb_fiber_scheduler_io_result(total, 0);
1192
+ #endif
868
1193
  }
869
1194
 
870
1195
  #pragma mark - IO#write
@@ -902,16 +1227,7 @@ io_write_ensure(VALUE _argument)
902
1227
  struct io_write_arguments *arguments = (struct io_write_arguments*)_argument;
903
1228
  struct IO_Event_Selector_URing *selector = arguments->selector;
904
1229
 
905
- // If the operation is still in progress, cancel it:
906
- if (arguments->waiting->completion) {
907
- if (DEBUG) fprintf(stderr, "io_write_ensure:io_uring_prep_cancel(waiting=%p, completion=%p)\n", (void*)arguments->waiting, (void*)arguments->waiting->completion);
908
- struct io_uring_sqe *sqe = io_get_sqe(selector);
909
- io_uring_prep_cancel(sqe, (void*)arguments->waiting->completion, 0);
910
- io_uring_sqe_set_data(sqe, NULL);
911
- io_uring_submit_now(selector);
912
- }
913
-
914
- IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
1230
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(selector, arguments->waiting);
915
1231
 
916
1232
  return Qnil;
917
1233
  }
@@ -941,20 +1257,87 @@ io_write(struct IO_Event_Selector_URing *selector, VALUE fiber, int descriptor,
941
1257
  );
942
1258
  }
943
1259
 
944
- VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) {
1260
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1261
+ struct io_write_locked_arguments {
1262
+ struct IO_Event_Selector_URing *selector;
1263
+ VALUE fiber;
1264
+ VALUE io;
1265
+ off_t from;
1266
+ size_t offset;
1267
+ size_t length;
1268
+ bool positional;
1269
+ };
1270
+
1271
+ static VALUE
1272
+ io_write_locked(const void *base, size_t size, VALUE _arguments)
1273
+ {
1274
+ struct io_write_locked_arguments *arguments = (struct io_write_locked_arguments *)_arguments;
1275
+
1276
+ if (!IO_Event_Selector_valid_buffer_range(size, arguments->offset, arguments->length)) {
1277
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
1278
+ } else if (arguments->length == 0) {
1279
+ return rb_fiber_scheduler_io_result(0, 0);
1280
+ }
1281
+
1282
+ int descriptor = IO_Event_Selector_io_descriptor(arguments->io);
1283
+ const char *buffer = (const char*)base + arguments->offset;
1284
+
1285
+ // Avoid the submission and suspension overhead when the operation can
1286
+ // complete immediately. The descriptor must be non-blocking for this
1287
+ // optimistic syscall; if it would block, restore its original mode and
1288
+ // submit the operation to io_uring instead.
1289
+ int flags = IO_Event_Selector_nonblock_set(descriptor);
1290
+ ssize_t result;
1291
+
1292
+ if (arguments->positional) {
1293
+ result = pwrite(descriptor, buffer, arguments->length, arguments->from);
1294
+ } else {
1295
+ result = write(descriptor, buffer, arguments->length);
1296
+ }
1297
+
1298
+ int error = errno;
1299
+ IO_Event_Selector_nonblock_restore(descriptor, flags);
1300
+
1301
+ if (result >= 0) {
1302
+ return rb_fiber_scheduler_io_result(result, 0);
1303
+ } else if (!IO_Event_try_again(error)) {
1304
+ return rb_fiber_scheduler_io_result(-1, error);
1305
+ }
1306
+
1307
+ off_t from = arguments->positional ? arguments->from : io_seekable(descriptor);
1308
+
1309
+ int completion = io_write(arguments->selector, arguments->fiber, descriptor, (char*)buffer, arguments->length, from);
1310
+ if (completion < 0) {
1311
+ return rb_fiber_scheduler_io_result(-1, -completion);
1312
+ }
1313
+
1314
+ return rb_fiber_scheduler_io_result(completion, 0);
1315
+ }
1316
+ #endif
1317
+
1318
+ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _first, VALUE _second) {
945
1319
  struct IO_Event_Selector_URing *selector = NULL;
946
1320
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
947
1321
 
948
- int descriptor = IO_Event_Selector_io_descriptor(io);
1322
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1323
+ struct io_write_locked_arguments arguments = {
1324
+ .selector = selector,
1325
+ .fiber = fiber,
1326
+ .io = io,
1327
+ .offset = NUM2SIZET(_first),
1328
+ .length = NUM2SIZET(_second),
1329
+ .positional = false,
1330
+ };
949
1331
 
1332
+ return rb_io_buffer_locked_for_reading(buffer, io_write_locked, (VALUE)&arguments);
1333
+ #else
950
1334
  const void *base;
951
1335
  size_t size;
952
1336
  rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
953
1337
 
954
- size_t length = NUM2SIZET(_length);
955
- size_t offset = NUM2SIZET(_offset);
1338
+ size_t length = NUM2SIZET(_first);
1339
+ size_t offset = NUM2SIZET(_second);
956
1340
  size_t total = 0;
957
- off_t from = io_seekable(descriptor);
958
1341
 
959
1342
  if (length > size) {
960
1343
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
@@ -963,8 +1346,12 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
963
1346
  // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
964
1347
  if (offset > size) {
965
1348
  return rb_fiber_scheduler_io_result(-1, EINVAL);
1349
+ } else if (offset == size) {
1350
+ return rb_fiber_scheduler_io_result(0, 0);
966
1351
  }
967
-
1352
+
1353
+ int descriptor = IO_Event_Selector_io_descriptor(io);
1354
+ off_t from = io_seekable(descriptor);
968
1355
  size_t maximum_size = size - offset;
969
1356
  while (maximum_size) {
970
1357
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -986,8 +1373,10 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
986
1373
  }
987
1374
 
988
1375
  return rb_fiber_scheduler_io_result(total, 0);
1376
+ #endif
989
1377
  }
990
1378
 
1379
+ #if RUBY_FIBER_SCHEDULER_VERSION < 4
991
1380
  static VALUE IO_Event_Selector_URing_io_write_compatible(int argc, VALUE *argv, VALUE self)
992
1381
  {
993
1382
  rb_check_arity(argc, 4, 5);
@@ -1000,21 +1389,32 @@ static VALUE IO_Event_Selector_URing_io_write_compatible(int argc, VALUE *argv,
1000
1389
 
1001
1390
  return IO_Event_Selector_URing_io_write(self, argv[0], argv[1], argv[2], argv[3], _offset);
1002
1391
  }
1392
+ #endif
1003
1393
 
1004
- VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _length, VALUE _offset) {
1394
+ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _first, VALUE _second) {
1005
1395
  struct IO_Event_Selector_URing *selector = NULL;
1006
1396
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
1007
1397
 
1008
- int descriptor = IO_Event_Selector_io_descriptor(io);
1398
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1399
+ struct io_write_locked_arguments arguments = {
1400
+ .selector = selector,
1401
+ .fiber = fiber,
1402
+ .io = io,
1403
+ .from = NUM2OFFT(_from),
1404
+ .offset = NUM2SIZET(_first),
1405
+ .length = NUM2SIZET(_second),
1406
+ .positional = true,
1407
+ };
1009
1408
 
1409
+ return rb_io_buffer_locked_for_reading(buffer, io_write_locked, (VALUE)&arguments);
1410
+ #else
1010
1411
  const void *base;
1011
1412
  size_t size;
1012
1413
  rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
1013
1414
 
1014
- size_t length = NUM2SIZET(_length);
1015
- size_t offset = NUM2SIZET(_offset);
1415
+ size_t length = NUM2SIZET(_first);
1416
+ size_t offset = NUM2SIZET(_second);
1016
1417
  size_t total = 0;
1017
- off_t from = NUM2OFFT(_from);
1018
1418
 
1019
1419
  if (length > size) {
1020
1420
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
@@ -1023,8 +1423,11 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
1023
1423
  // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
1024
1424
  if (offset > size) {
1025
1425
  return rb_fiber_scheduler_io_result(-1, EINVAL);
1426
+ } else if (offset == size) {
1427
+ return rb_fiber_scheduler_io_result(0, 0);
1026
1428
  }
1027
-
1429
+ off_t from = NUM2OFFT(_from);
1430
+ int descriptor = IO_Event_Selector_io_descriptor(io);
1028
1431
  size_t maximum_size = size - offset;
1029
1432
  while (maximum_size) {
1030
1433
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -1047,6 +1450,7 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
1047
1450
  }
1048
1451
 
1049
1452
  return rb_fiber_scheduler_io_result(total, 0);
1453
+ #endif
1050
1454
  }
1051
1455
 
1052
1456
  #endif
@@ -1104,9 +1508,14 @@ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec
1104
1508
  return storage;
1105
1509
  }
1106
1510
 
1511
+ // Return true when it is safe and useful to enter the blocking selector wait.
1107
1512
  static
1108
- int timeout_nonblocking(struct __kernel_timespec *timespec) {
1109
- return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
1513
+ int select_blocking_allowed(struct __kernel_timespec *timespec) {
1514
+ // A `0` timeout is already a poll. The selector has already performed the immediate poll previously, so there is no useful blocking wait to enter here.
1515
+ if (timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0) {
1516
+ return 0;
1517
+ }
1518
+ return 1;
1110
1519
  }
1111
1520
 
1112
1521
  struct select_arguments {
@@ -1145,22 +1554,21 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
1145
1554
 
1146
1555
  io_uring_submit_flush(selector);
1147
1556
 
1148
- selector->blocked = 1;
1149
- rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
1150
- selector->blocked = 0;
1557
+ arguments->result = -EINTR;
1558
+ IO_Event_Selector_blocking_operation(&selector->backend, select_internal, (void *)arguments, RUBY_UBF_IO, 0);
1151
1559
 
1152
1560
  if (arguments->result == -ETIME) {
1153
- arguments->result = 0;
1561
+ return 0;
1154
1562
  } else if (arguments->result == -EINTR) {
1155
- arguments->result = 0;
1563
+ return 0;
1156
1564
  } else if (arguments->result < 0) {
1157
1565
  rb_syserr_fail(-arguments->result, "select_internal_without_gvl:io_uring_wait_cqe_timeout");
1158
1566
  } else {
1159
1567
  // At least 1 event is waiting:
1160
- arguments->result = 1;
1568
+ return 1;
1161
1569
  }
1162
1570
 
1163
- return arguments->result;
1571
+ return 0;
1164
1572
  }
1165
1573
 
1166
1574
  static inline
@@ -1194,6 +1602,13 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1194
1602
  continue;
1195
1603
  }
1196
1604
 
1605
+ if (cqe->user_data & IO_EVENT_SELECTOR_URING_CANCELLATION_TAG) {
1606
+ struct IO_Event_Selector_URing_Completion *completion = IO_Event_Selector_URing_Completion_from_cancellation_data(cqe->user_data);
1607
+ IO_Event_Selector_URing_Completion_cancellation_complete(selector, completion);
1608
+ io_uring_cq_advance(ring, 1);
1609
+ continue;
1610
+ }
1611
+
1197
1612
  struct IO_Event_Selector_URing_Completion *completion = (void*)cqe->user_data;
1198
1613
  struct IO_Event_Selector_URing_Waiting *waiting = completion->waiting;
1199
1614
 
@@ -1208,13 +1623,11 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1208
1623
 
1209
1624
  VALUE fiber = 0;
1210
1625
  if (waiting && waiting->fiber) {
1211
- assert(waiting->result != -ECANCELED);
1212
-
1213
1626
  fiber = waiting->fiber;
1214
1627
  }
1215
1628
 
1216
1629
  // This marks the waiting operation as "complete":
1217
- IO_Event_Selector_URing_Completion_release(selector, completion);
1630
+ IO_Event_Selector_URing_Completion_complete(selector, completion);
1218
1631
 
1219
1632
  if (fiber) {
1220
1633
  IO_Event_Selector_loop_resume(&selector->backend, fiber, 0, NULL);
@@ -1257,28 +1670,29 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
1257
1670
 
1258
1671
  int ready = IO_Event_Selector_ready_flush(&selector->backend);
1259
1672
 
1260
- int result = select_process_completions(selector);
1673
+ int completed = select_process_completions(selector);
1261
1674
 
1262
1675
  // If we:
1263
1676
  // 1. Didn't process any ready fibers, and
1264
1677
  // 2. Didn't process any events from non-blocking select (above), and
1265
1678
  // 3. There are no items in the ready list,
1266
1679
  // then we can perform a blocking select.
1267
- if (!ready && !result && !selector->backend.ready) {
1680
+ if (!ready && !completed && !selector->backend.ready) {
1268
1681
  // We might need to wait for events:
1269
1682
  struct select_arguments arguments = {
1270
1683
  .selector = selector,
1684
+ .result = 0,
1271
1685
  .timeout = NULL,
1272
1686
  };
1273
1687
 
1274
1688
  arguments.timeout = make_timeout(duration, &arguments.storage);
1275
1689
 
1276
- if (!selector->backend.ready && !timeout_nonblocking(arguments.timeout)) {
1690
+ if (!selector->backend.ready && select_blocking_allowed(arguments.timeout)) {
1277
1691
  struct timespec start_time;
1278
1692
  IO_Event_Time_current(&start_time);
1279
1693
 
1280
1694
  // This is a blocking operation, we wait for events:
1281
- result = select_internal_without_gvl(&arguments);
1695
+ int result = select_internal_without_gvl(&arguments);
1282
1696
 
1283
1697
  struct timespec end_time;
1284
1698
  IO_Event_Time_current(&end_time);
@@ -1286,12 +1700,12 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
1286
1700
 
1287
1701
  // After waiting/flushing the SQ, check if there are any completions:
1288
1702
  if (result > 0) {
1289
- result = select_process_completions(selector);
1703
+ completed = select_process_completions(selector);
1290
1704
  }
1291
1705
  }
1292
1706
  }
1293
1707
 
1294
- return RB_INT2NUM(result);
1708
+ return RB_INT2NUM(completed);
1295
1709
  }
1296
1710
 
1297
1711
  VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
@@ -1300,7 +1714,7 @@ VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
1300
1714
 
1301
1715
  // Wake the selector by signalling the interrupt. This is safe from any thread
1302
1716
  // and never touches the ring's SQ, which is required for IORING_SETUP_SINGLE_ISSUER.
1303
- if (selector->blocked) {
1717
+ if (selector->backend.blocked) {
1304
1718
  IO_Event_Interrupt_signal(&selector->interrupt);
1305
1719
  return Qtrue;
1306
1720
  }
@@ -1370,12 +1784,18 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
1370
1784
  rb_define_method(IO_Event_Selector_URing, "select", IO_Event_Selector_URing_select, 1);
1371
1785
  rb_define_method(IO_Event_Selector_URing, "wakeup", IO_Event_Selector_URing_wakeup, 0);
1372
1786
  rb_define_method(IO_Event_Selector_URing, "close", IO_Event_Selector_URing_close, 0);
1787
+ rb_define_method(IO_Event_Selector_URing, "closed?", IO_Event_Selector_URing_closed_p, 0);
1373
1788
 
1374
1789
  rb_define_method(IO_Event_Selector_URing, "io_wait", IO_Event_Selector_URing_io_wait, 3);
1375
1790
 
1376
1791
  #ifdef HAVE_RUBY_IO_BUFFER_H
1792
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1793
+ rb_define_method(IO_Event_Selector_URing, "io_read", IO_Event_Selector_URing_io_read, 5);
1794
+ rb_define_method(IO_Event_Selector_URing, "io_write", IO_Event_Selector_URing_io_write, 5);
1795
+ #else
1377
1796
  rb_define_method(IO_Event_Selector_URing, "io_read", IO_Event_Selector_URing_io_read_compatible, -1);
1378
1797
  rb_define_method(IO_Event_Selector_URing, "io_write", IO_Event_Selector_URing_io_write_compatible, -1);
1798
+ #endif
1379
1799
  rb_define_method(IO_Event_Selector_URing, "io_pread", IO_Event_Selector_URing_io_pread, 6);
1380
1800
  rb_define_method(IO_Event_Selector_URing, "io_pwrite", IO_Event_Selector_URing_io_pwrite, 6);
1381
1801
  #endif