io-event 1.16.4 → 1.18.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83f9faed054855a0c3a50ab469f315c1345a8af43641ab6c5630d648763fc55d
4
- data.tar.gz: 78114e047cd897ed0192c262d099a93baea21d0c30ddb70cd57e4efc73a40889
3
+ metadata.gz: 7ad9ab9326ef0bb4d637833e5d9705940a7d150c0e42154ee4642fbf6f4b4b49
4
+ data.tar.gz: bb1274afaa893df427b3eb422aa5c198da08493126e8b5f3ee2daac375f1ebda
5
5
  SHA512:
6
- metadata.gz: 7cea5251f1c5b93b0235e2aa6aa69c65913e13de847a535eee21343060f744b2a810eb901b15b82bbb3c9979d60a620d4d89dfba349473ab6836e9c420985b9d
7
- data.tar.gz: c8752f8a0bc9da0ca37ae420364a564c68ad4cb6020124f767677a854c7d81639e9e6daa46040d8fc575e02a7231ff5409d72833b8eb15ec72684b063b5d92d2
6
+ metadata.gz: 543bdb171056e37849b7042c0d822e7421908bbd006227be92b1b2c5c12d7988c8201ce02316c9f0aa32df9746f4a6d88345c8eb9f38f9c5018f4adbaeca27b9
7
+ data.tar.gz: 65b3f2b61cf38b3c66cb2d0e9299ece198e546d0e30676f8bbd616fdccb043b47f032f0e8fd00bb5bf14bdef471f5ebb1c4769e8de86654900916fc8952c2be7
checksums.yaml.gz.sig CHANGED
Binary file
@@ -9,6 +9,7 @@
9
9
  #include <sys/epoll.h>
10
10
  #include <time.h>
11
11
  #include <errno.h>
12
+ #include <unistd.h>
12
13
 
13
14
  #include "pidfd.c"
14
15
  #include "../interrupt.h"
@@ -38,6 +39,7 @@ struct IO_Event_Selector_EPoll
38
39
  {
39
40
  struct IO_Event_Selector backend;
40
41
  int descriptor;
42
+ pid_t owner;
41
43
 
42
44
  // Flag indicating whether the selector is currently blocked in a system call.
43
45
  // Set to 1 when blocked in epoll_wait() without GVL, 0 otherwise.
@@ -131,10 +133,12 @@ static
131
133
  void close_internal(struct IO_Event_Selector_EPoll *selector)
132
134
  {
133
135
  if (selector->descriptor >= 0) {
134
- close(selector->descriptor);
135
- selector->descriptor = -1;
136
+ if (selector->owner == getpid()) {
137
+ close(selector->descriptor);
138
+ IO_Event_Interrupt_close(&selector->interrupt);
139
+ }
136
140
 
137
- IO_Event_Interrupt_close(&selector->interrupt);
141
+ selector->descriptor = -1;
138
142
  }
139
143
  }
140
144
 
@@ -315,6 +319,7 @@ VALUE IO_Event_Selector_EPoll_allocate(VALUE self) {
315
319
 
316
320
  IO_Event_Selector_initialize(&selector->backend, self, Qnil);
317
321
  selector->descriptor = -1;
322
+ selector->owner = 0;
318
323
  selector->blocked = 0;
319
324
 
320
325
  selector->descriptors.element_initialize = IO_Event_Selector_EPoll_Descriptor_initialize;
@@ -350,6 +355,7 @@ VALUE IO_Event_Selector_EPoll_initialize(VALUE self, VALUE loop) {
350
355
  rb_sys_fail("IO_Event_Selector_EPoll_initialize:epoll_create");
351
356
  } else {
352
357
  selector->descriptor = result;
358
+ selector->owner = getpid();
353
359
 
354
360
  rb_update_max_fd(selector->descriptor);
355
361
  }
@@ -385,6 +391,13 @@ VALUE IO_Event_Selector_EPoll_close(VALUE self) {
385
391
  return Qnil;
386
392
  }
387
393
 
394
+ VALUE IO_Event_Selector_EPoll_closed_p(VALUE self) {
395
+ struct IO_Event_Selector_EPoll *selector = NULL;
396
+ TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, selector);
397
+
398
+ return selector->descriptor < 0 || selector->owner != getpid() ? Qtrue : Qfalse;
399
+ }
400
+
388
401
  VALUE IO_Event_Selector_EPoll_transfer(VALUE self)
389
402
  {
390
403
  struct IO_Event_Selector_EPoll *selector = NULL;
@@ -590,36 +603,29 @@ struct io_read_arguments {
590
603
 
591
604
  int descriptor;
592
605
 
593
- VALUE buffer;
606
+ // The remaining writable buffer region after applying the requested offset.
607
+ void *base;
608
+ size_t size;
609
+
610
+ // The minimum number of bytes requested by the caller.
594
611
  size_t length;
595
- size_t offset;
596
612
  };
597
613
 
598
614
  static
599
615
  VALUE io_read_loop(VALUE _arguments) {
600
616
  struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments;
601
617
 
602
- void *base;
603
- size_t size;
604
- rb_io_buffer_get_bytes_for_writing(arguments->buffer, &base, &size);
605
-
606
618
  size_t length = arguments->length;
607
- size_t offset = arguments->offset;
608
619
  size_t total = 0;
609
620
 
610
- // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
611
- if (offset > size) {
612
- return rb_fiber_scheduler_io_result(-1, EINVAL);
613
- }
614
-
615
- size_t maximum_size = size - offset;
621
+ size_t maximum_size = arguments->size;
616
622
  while (maximum_size) {
617
- ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size);
623
+ ssize_t result = read(arguments->descriptor, (char*)arguments->base+total, maximum_size);
618
624
 
619
625
  if (result > 0) {
620
626
  total += result;
621
- offset += result;
622
627
  if ((size_t)result >= length) break;
628
+ maximum_size -= result;
623
629
  length -= result;
624
630
  } else if (result == 0) {
625
631
  break;
@@ -628,8 +634,6 @@ VALUE io_read_loop(VALUE _arguments) {
628
634
  } else {
629
635
  return rb_fiber_scheduler_io_result(-1, errno);
630
636
  }
631
-
632
- maximum_size = size - offset;
633
637
  }
634
638
 
635
639
  return rb_fiber_scheduler_io_result(total, 0);
@@ -645,11 +649,24 @@ VALUE io_read_ensure(VALUE _arguments) {
645
649
  }
646
650
 
647
651
  VALUE IO_Event_Selector_EPoll_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) {
648
- int descriptor = IO_Event_Selector_io_descriptor(io);
649
-
650
652
  size_t offset = NUM2SIZET(_offset);
651
653
  size_t length = NUM2SIZET(_length);
652
654
 
655
+ void *base;
656
+ size_t size;
657
+ rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
658
+
659
+ if (offset > size) {
660
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
661
+ } else if (offset == size) {
662
+ return rb_fiber_scheduler_io_result(0, 0);
663
+ }
664
+
665
+ base = (char*)base + offset;
666
+ size -= offset;
667
+
668
+ int descriptor = IO_Event_Selector_io_descriptor(io);
669
+
653
670
  struct io_read_arguments io_read_arguments = {
654
671
  .self = self,
655
672
  .fiber = fiber,
@@ -657,9 +674,9 @@ VALUE IO_Event_Selector_EPoll_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
657
674
 
658
675
  .flags = IO_Event_Selector_nonblock_set(descriptor),
659
676
  .descriptor = descriptor,
660
- .buffer = buffer,
677
+ .base = base,
678
+ .size = size,
661
679
  .length = length,
662
- .offset = offset,
663
680
  };
664
681
 
665
682
  RB_OBJ_WRITTEN(self, Qundef, fiber);
@@ -689,40 +706,29 @@ struct io_write_arguments {
689
706
 
690
707
  int descriptor;
691
708
 
692
- VALUE buffer;
709
+ // The remaining readable buffer region after applying the requested offset.
710
+ const void *base;
711
+ size_t size;
712
+
713
+ // The minimum number of bytes requested by the caller.
693
714
  size_t length;
694
- size_t offset;
695
715
  };
696
716
 
697
717
  static
698
718
  VALUE io_write_loop(VALUE _arguments) {
699
719
  struct io_write_arguments *arguments = (struct io_write_arguments *)_arguments;
700
720
 
701
- const void *base;
702
- size_t size;
703
- rb_io_buffer_get_bytes_for_reading(arguments->buffer, &base, &size);
704
-
705
721
  size_t length = arguments->length;
706
- size_t offset = arguments->offset;
707
722
  size_t total = 0;
708
723
 
709
- if (length > size) {
710
- rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
711
- }
712
-
713
- // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
714
- if (offset > size) {
715
- return rb_fiber_scheduler_io_result(-1, EINVAL);
716
- }
717
-
718
- size_t maximum_size = size - offset;
724
+ size_t maximum_size = arguments->size;
719
725
  while (maximum_size) {
720
- ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size);
726
+ ssize_t result = write(arguments->descriptor, (char*)arguments->base+total, maximum_size);
721
727
 
722
728
  if (result > 0) {
723
729
  total += result;
724
- offset += result;
725
730
  if ((size_t)result >= length) break;
731
+ maximum_size -= result;
726
732
  length -= result;
727
733
  } else if (result == 0) {
728
734
  break;
@@ -731,8 +737,6 @@ VALUE io_write_loop(VALUE _arguments) {
731
737
  } else {
732
738
  return rb_fiber_scheduler_io_result(-1, errno);
733
739
  }
734
-
735
- maximum_size = size - offset;
736
740
  }
737
741
 
738
742
  return rb_fiber_scheduler_io_result(total, 0);
@@ -748,11 +752,28 @@ VALUE io_write_ensure(VALUE _arguments) {
748
752
  };
749
753
 
750
754
  VALUE IO_Event_Selector_EPoll_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) {
751
- int descriptor = IO_Event_Selector_io_descriptor(io);
752
-
753
755
  size_t length = NUM2SIZET(_length);
754
756
  size_t offset = NUM2SIZET(_offset);
755
757
 
758
+ const void *base;
759
+ size_t size;
760
+ rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
761
+
762
+ if (length > size) {
763
+ rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
764
+ }
765
+
766
+ if (offset > size) {
767
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
768
+ } else if (offset == size) {
769
+ return rb_fiber_scheduler_io_result(0, 0);
770
+ }
771
+
772
+ base = (const char*)base + offset;
773
+ size -= offset;
774
+
775
+ int descriptor = IO_Event_Selector_io_descriptor(io);
776
+
756
777
  struct io_write_arguments io_write_arguments = {
757
778
  .self = self,
758
779
  .fiber = fiber,
@@ -760,9 +781,9 @@ VALUE IO_Event_Selector_EPoll_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
760
781
 
761
782
  .flags = IO_Event_Selector_nonblock_set(descriptor),
762
783
  .descriptor = descriptor,
763
- .buffer = buffer,
784
+ .base = base,
785
+ .size = size,
764
786
  .length = length,
765
- .offset = offset,
766
787
  };
767
788
 
768
789
  RB_OBJ_WRITTEN(self, Qundef, fiber);
@@ -809,14 +830,33 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
809
830
  }
810
831
 
811
832
  static
812
- int timeout_nonblocking(struct timespec * timespec) {
833
+ int timeout_is_nonblocking(struct timespec * timespec) {
813
834
  return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
814
835
  }
815
836
 
837
+ // Return true when it is safe and useful to enter the blocking selector wait.
838
+ static
839
+ int select_blocking_allowed(struct timespec * timespec) {
840
+ // 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.
841
+ if (timeout_is_nonblocking(timespec)) {
842
+ return 0;
843
+ }
844
+
845
+ #ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
846
+ // On Rubies without `RB_NOGVL_PENDING_INTERRUPT_FAIL`, `rb_thread_call_without_gvl2` can enter an indefinite native wait even if a masked interrupt is already pending for this thread. This is the last safe point to avoid that wait: we still hold the GVL, so the pending interrupt queue cannot change concurrently before we decide whether to enter the blocking path.
847
+ if (IO_Event_Selector_pending_interrupt()) {
848
+ return 0;
849
+ }
850
+ #endif
851
+
852
+ return 1;
853
+ }
854
+
816
855
  struct select_arguments {
817
856
  struct IO_Event_Selector_EPoll *selector;
818
857
 
819
858
  int count;
859
+ int result;
820
860
  struct epoll_event events[EPOLL_MAX_EVENTS];
821
861
 
822
862
  struct timespec * timeout;
@@ -830,7 +870,7 @@ static int make_timeout_ms(struct timespec * timeout) {
830
870
  return -1;
831
871
  }
832
872
 
833
- if (timeout_nonblocking(timeout)) {
873
+ if (timeout_is_nonblocking(timeout)) {
834
874
  return 0;
835
875
  }
836
876
 
@@ -851,13 +891,13 @@ void * select_internal(void *_arguments) {
851
891
  struct select_arguments * arguments = (struct select_arguments *)_arguments;
852
892
 
853
893
  #if defined(HAVE_EPOLL_PWAIT2)
854
- arguments->count = epoll_pwait2(arguments->selector->descriptor, arguments->events, EPOLL_MAX_EVENTS, arguments->timeout, NULL);
894
+ arguments->result = epoll_pwait2(arguments->selector->descriptor, arguments->events, arguments->count, arguments->timeout, NULL);
855
895
 
856
896
  // Comment out the above line and enable the below lines to test ENOSYS code path.
857
- // arguments->count = -1;
897
+ // arguments->result = -1;
858
898
  // errno = ENOSYS;
859
899
 
860
- if (!enosys_error(arguments->count)) {
900
+ if (!enosys_error(arguments->result)) {
861
901
  return NULL;
862
902
  }
863
903
  else {
@@ -865,37 +905,47 @@ void * select_internal(void *_arguments) {
865
905
  }
866
906
  #endif
867
907
 
868
- arguments->count = epoll_wait(arguments->selector->descriptor, arguments->events, EPOLL_MAX_EVENTS, make_timeout_ms(arguments->timeout));
908
+ arguments->result = epoll_wait(arguments->selector->descriptor, arguments->events, arguments->count, make_timeout_ms(arguments->timeout));
869
909
 
870
910
  return NULL;
871
911
  }
872
912
 
873
913
  static
874
- void select_internal_without_gvl(struct select_arguments *arguments) {
914
+ int select_internal_without_gvl(struct select_arguments *arguments) {
915
+ arguments->result = -1;
875
916
  arguments->selector->blocked = 1;
876
- rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
917
+ #ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
918
+ rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTERRUPT_FAIL);
919
+ #else
920
+ rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
921
+ #endif
877
922
  arguments->selector->blocked = 0;
878
923
 
879
- if (arguments->count == -1) {
880
- if (errno != EINTR) {
924
+ if (arguments->result == -1) {
925
+ // If Ruby skips the native callback, the result sentinel can remain `-1`; `errno` may be `0` or `EINTR` depending on the Ruby implementation. Both cases mean the blocking wait did not produce any events.
926
+ if (errno != EINTR && errno != 0) {
881
927
  rb_sys_fail("select_internal_without_gvl:epoll_wait");
882
928
  } else {
883
- arguments->count = 0;
929
+ return 0;
884
930
  }
885
931
  }
932
+
933
+ return arguments->result;
886
934
  }
887
935
 
888
936
  static
889
- void select_internal_with_gvl(struct select_arguments *arguments) {
937
+ int select_internal_with_gvl(struct select_arguments *arguments) {
890
938
  select_internal((void *)arguments);
891
939
 
892
- if (arguments->count == -1) {
940
+ if (arguments->result == -1) {
893
941
  if (errno != EINTR) {
894
942
  rb_sys_fail("select_internal_with_gvl:epoll_wait");
895
943
  } else {
896
- arguments->count = 0;
944
+ return 0;
897
945
  }
898
946
  }
947
+
948
+ return arguments->result;
899
949
  }
900
950
 
901
951
  static
@@ -951,7 +1001,7 @@ VALUE select_handle_events(VALUE _arguments)
951
1001
  struct select_arguments *arguments = (struct select_arguments *)_arguments;
952
1002
  struct IO_Event_Selector_EPoll *selector = arguments->selector;
953
1003
 
954
- for (int i = 0; i < arguments->count; i += 1) {
1004
+ for (int i = 0; i < arguments->result; i += 1) {
955
1005
  const struct epoll_event *event = &arguments->events[i];
956
1006
  if (DEBUG) fprintf(stderr, "-> fd=%d events=%d\n", event->data.fd, event->events);
957
1007
 
@@ -962,7 +1012,7 @@ VALUE select_handle_events(VALUE _arguments)
962
1012
  }
963
1013
  }
964
1014
 
965
- return INT2NUM(arguments->count);
1015
+ return INT2NUM(arguments->result);
966
1016
  }
967
1017
 
968
1018
  static
@@ -987,6 +1037,8 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
987
1037
 
988
1038
  struct select_arguments arguments = {
989
1039
  .selector = selector,
1040
+ .count = EPOLL_MAX_EVENTS,
1041
+ .result = 0,
990
1042
  .storage = {
991
1043
  .tv_sec = 0,
992
1044
  .tv_nsec = 0
@@ -997,22 +1049,22 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
997
1049
  arguments.timeout = &arguments.storage;
998
1050
 
999
1051
  // Process any currently pending events:
1000
- select_internal_with_gvl(&arguments);
1052
+ int result = select_internal_with_gvl(&arguments);
1001
1053
 
1002
1054
  // If we:
1003
1055
  // 1. Didn't process any ready fibers, and
1004
1056
  // 2. Didn't process any events from non-blocking select (above), and
1005
1057
  // 3. There are no items in the ready list,
1006
1058
  // then we can perform a blocking select.
1007
- if (!ready && !arguments.count && !selector->backend.ready) {
1059
+ if (!ready && !result && !selector->backend.ready) {
1008
1060
  arguments.timeout = make_timeout(duration, &arguments.storage);
1009
1061
 
1010
- if (!timeout_nonblocking(arguments.timeout)) {
1062
+ if (select_blocking_allowed(arguments.timeout)) {
1011
1063
  struct timespec start_time;
1012
1064
  IO_Event_Time_current(&start_time);
1013
1065
 
1014
1066
  // Wait for events to occur:
1015
- select_internal_without_gvl(&arguments);
1067
+ result = select_internal_without_gvl(&arguments);
1016
1068
 
1017
1069
  struct timespec end_time;
1018
1070
  IO_Event_Time_current(&end_time);
@@ -1020,7 +1072,7 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
1020
1072
  }
1021
1073
  }
1022
1074
 
1023
- if (arguments.count) {
1075
+ if (result) {
1024
1076
  return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments);
1025
1077
  } else {
1026
1078
  return RB_INT2NUM(0);
@@ -1079,6 +1131,7 @@ void Init_IO_Event_Selector_EPoll(VALUE IO_Event_Selector) {
1079
1131
  rb_define_method(IO_Event_Selector_EPoll, "select", IO_Event_Selector_EPoll_select, 1);
1080
1132
  rb_define_method(IO_Event_Selector_EPoll, "wakeup", IO_Event_Selector_EPoll_wakeup, 0);
1081
1133
  rb_define_method(IO_Event_Selector_EPoll, "close", IO_Event_Selector_EPoll_close, 0);
1134
+ rb_define_method(IO_Event_Selector_EPoll, "closed?", IO_Event_Selector_EPoll_closed_p, 0);
1082
1135
 
1083
1136
  rb_define_method(IO_Event_Selector_EPoll, "io_wait", IO_Event_Selector_EPoll_io_wait, 3);
1084
1137
 
@@ -12,6 +12,7 @@
12
12
  #include <errno.h>
13
13
  #include <sys/wait.h>
14
14
  #include <signal.h>
15
+ #include <unistd.h>
15
16
 
16
17
  #include "../interrupt.h"
17
18
 
@@ -47,6 +48,7 @@ struct IO_Event_Selector_KQueue
47
48
  {
48
49
  struct IO_Event_Selector backend;
49
50
  int descriptor;
51
+ pid_t owner;
50
52
 
51
53
  // Flag indicating whether the selector is currently blocked in a system call.
52
54
  // Set to 1 when blocked in kevent() without GVL, 0 otherwise.
@@ -132,7 +134,14 @@ static
132
134
  void close_internal(struct IO_Event_Selector_KQueue *selector)
133
135
  {
134
136
  if (selector->descriptor >= 0) {
135
- close(selector->descriptor);
137
+ if (selector->owner == getpid()) {
138
+ close(selector->descriptor);
139
+
140
+ #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT
141
+ IO_Event_Interrupt_close(&selector->interrupt);
142
+ #endif
143
+ }
144
+
136
145
  selector->descriptor = -1;
137
146
  }
138
147
  }
@@ -289,6 +298,7 @@ VALUE IO_Event_Selector_KQueue_allocate(VALUE self) {
289
298
 
290
299
  IO_Event_Selector_initialize(&selector->backend, self, Qnil);
291
300
  selector->descriptor = -1;
301
+ selector->owner = 0;
292
302
  selector->blocked = 0;
293
303
 
294
304
  selector->descriptors.element_initialize = IO_Event_Selector_KQueue_Descriptor_initialize;
@@ -331,6 +341,7 @@ VALUE IO_Event_Selector_KQueue_initialize(VALUE self, VALUE loop) {
331
341
  ioctl(result, FIOCLEX);
332
342
 
333
343
  selector->descriptor = result;
344
+ selector->owner = getpid();
334
345
 
335
346
  rb_update_max_fd(selector->descriptor);
336
347
  }
@@ -365,13 +376,16 @@ VALUE IO_Event_Selector_KQueue_close(VALUE self) {
365
376
 
366
377
  close_internal(selector);
367
378
 
368
- #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT
369
- IO_Event_Interrupt_close(&selector->interrupt);
370
- #endif
371
-
372
379
  return Qnil;
373
380
  }
374
381
 
382
+ VALUE IO_Event_Selector_KQueue_closed_p(VALUE self) {
383
+ struct IO_Event_Selector_KQueue *selector = NULL;
384
+ TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
385
+
386
+ return selector->descriptor < 0 || selector->owner != getpid() ? Qtrue : Qfalse;
387
+ }
388
+
375
389
  VALUE IO_Event_Selector_KQueue_transfer(VALUE self)
376
390
  {
377
391
  struct IO_Event_Selector_KQueue *selector = NULL;
@@ -578,40 +592,33 @@ struct io_read_arguments {
578
592
 
579
593
  int descriptor;
580
594
 
581
- VALUE buffer;
595
+ // The remaining writable buffer region after applying the requested offset.
596
+ void *base;
597
+ size_t size;
598
+
599
+ // The minimum number of bytes requested by the caller.
582
600
  size_t length;
583
- size_t offset;
584
601
  };
585
602
 
586
603
  static
587
604
  VALUE io_read_loop(VALUE _arguments) {
588
605
  struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments;
589
606
 
590
- void *base;
591
- size_t size;
592
- rb_io_buffer_get_bytes_for_writing(arguments->buffer, &base, &size);
593
-
594
607
  size_t length = arguments->length;
595
- size_t offset = arguments->offset;
596
608
  size_t total = 0;
597
609
 
598
610
  if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu)\n", arguments->descriptor, length);
599
611
 
600
- // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
601
- if (offset > size) {
602
- return rb_fiber_scheduler_io_result(-1, EINVAL);
603
- }
604
-
605
- size_t maximum_size = size - offset;
612
+ size_t maximum_size = arguments->size;
606
613
  while (maximum_size) {
607
- if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld)\n", arguments->descriptor, offset, maximum_size);
608
- ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size);
609
- if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, offset, maximum_size, result);
614
+ if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld)\n", arguments->descriptor, total, maximum_size);
615
+ ssize_t result = read(arguments->descriptor, (char*)arguments->base+total, maximum_size);
616
+ if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, total, maximum_size, result);
610
617
 
611
618
  if (result > 0) {
612
619
  total += result;
613
- offset += result;
614
620
  if ((size_t)result >= length) break;
621
+ maximum_size -= result;
615
622
  length -= result;
616
623
  } else if (result == 0) {
617
624
  break;
@@ -622,11 +629,9 @@ VALUE io_read_loop(VALUE _arguments) {
622
629
  if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno);
623
630
  return rb_fiber_scheduler_io_result(-1, errno);
624
631
  }
625
-
626
- maximum_size = size - offset;
627
632
  }
628
633
 
629
- if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset);
634
+ if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, total);
630
635
  return rb_fiber_scheduler_io_result(total, 0);
631
636
  }
632
637
 
@@ -643,11 +648,24 @@ VALUE IO_Event_Selector_KQueue_io_read(VALUE self, VALUE fiber, VALUE io, VALUE
643
648
  struct IO_Event_Selector_KQueue *selector = NULL;
644
649
  TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
645
650
 
646
- int descriptor = IO_Event_Selector_io_descriptor(io);
647
-
648
651
  size_t length = NUM2SIZET(_length);
649
652
  size_t offset = NUM2SIZET(_offset);
650
653
 
654
+ void *base;
655
+ size_t size;
656
+ rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
657
+
658
+ if (offset > size) {
659
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
660
+ } else if (offset == size) {
661
+ return rb_fiber_scheduler_io_result(0, 0);
662
+ }
663
+
664
+ base = (char*)base + offset;
665
+ size -= offset;
666
+
667
+ int descriptor = IO_Event_Selector_io_descriptor(io);
668
+
651
669
  struct io_read_arguments io_read_arguments = {
652
670
  .self = self,
653
671
  .fiber = fiber,
@@ -655,9 +673,9 @@ VALUE IO_Event_Selector_KQueue_io_read(VALUE self, VALUE fiber, VALUE io, VALUE
655
673
 
656
674
  .flags = IO_Event_Selector_nonblock_set(descriptor),
657
675
  .descriptor = descriptor,
658
- .buffer = buffer,
676
+ .base = base,
677
+ .size = size,
659
678
  .length = length,
660
- .offset = offset,
661
679
  };
662
680
 
663
681
  RB_OBJ_WRITTEN(self, Qundef, fiber);
@@ -687,44 +705,33 @@ struct io_write_arguments {
687
705
 
688
706
  int descriptor;
689
707
 
690
- VALUE buffer;
708
+ // The remaining readable buffer region after applying the requested offset.
709
+ const void *base;
710
+ size_t size;
711
+
712
+ // The minimum number of bytes requested by the caller.
691
713
  size_t length;
692
- size_t offset;
693
714
  };
694
715
 
695
716
  static
696
717
  VALUE io_write_loop(VALUE _arguments) {
697
718
  struct io_write_arguments *arguments = (struct io_write_arguments *)_arguments;
698
719
 
699
- const void *base;
700
- size_t size;
701
- rb_io_buffer_get_bytes_for_reading(arguments->buffer, &base, &size);
702
-
703
720
  size_t length = arguments->length;
704
- size_t offset = arguments->offset;
705
721
  size_t total = 0;
706
722
 
707
- if (length > size) {
708
- rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
709
- }
710
-
711
723
  if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu)\n", arguments->descriptor, length);
712
724
 
713
- // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
714
- if (offset > size) {
715
- return rb_fiber_scheduler_io_result(-1, EINVAL);
716
- }
717
-
718
- size_t maximum_size = size - offset;
725
+ size_t maximum_size = arguments->size;
719
726
  while (maximum_size) {
720
- if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, offset, maximum_size, length);
721
- ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size);
722
- if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, offset, maximum_size, result);
727
+ if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, total, maximum_size, length);
728
+ ssize_t result = write(arguments->descriptor, (char*)arguments->base+total, maximum_size);
729
+ if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, total, maximum_size, result);
723
730
 
724
731
  if (result > 0) {
725
732
  total += result;
726
- offset += result;
727
733
  if ((size_t)result >= length) break;
734
+ maximum_size -= result;
728
735
  length -= result;
729
736
  } else if (result == 0) {
730
737
  break;
@@ -735,11 +742,9 @@ VALUE io_write_loop(VALUE _arguments) {
735
742
  if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno);
736
743
  return rb_fiber_scheduler_io_result(-1, errno);
737
744
  }
738
-
739
- maximum_size = size - offset;
740
745
  }
741
746
 
742
- if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset);
747
+ if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, total);
743
748
  return rb_fiber_scheduler_io_result(total, 0);
744
749
  };
745
750
 
@@ -756,11 +761,28 @@ VALUE IO_Event_Selector_KQueue_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
756
761
  struct IO_Event_Selector_KQueue *selector = NULL;
757
762
  TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
758
763
 
759
- int descriptor = IO_Event_Selector_io_descriptor(io);
760
-
761
764
  size_t length = NUM2SIZET(_length);
762
765
  size_t offset = NUM2SIZET(_offset);
763
766
 
767
+ const void *base;
768
+ size_t size;
769
+ rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
770
+
771
+ if (length > size) {
772
+ rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
773
+ }
774
+
775
+ if (offset > size) {
776
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
777
+ } else if (offset == size) {
778
+ return rb_fiber_scheduler_io_result(0, 0);
779
+ }
780
+
781
+ base = (const char*)base + offset;
782
+ size -= offset;
783
+
784
+ int descriptor = IO_Event_Selector_io_descriptor(io);
785
+
764
786
  struct io_write_arguments io_write_arguments = {
765
787
  .self = self,
766
788
  .fiber = fiber,
@@ -768,9 +790,9 @@ VALUE IO_Event_Selector_KQueue_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
768
790
 
769
791
  .flags = IO_Event_Selector_nonblock_set(descriptor),
770
792
  .descriptor = descriptor,
771
- .buffer = buffer,
793
+ .base = base,
794
+ .size = size,
772
795
  .length = length,
773
- .offset = offset,
774
796
  };
775
797
 
776
798
  RB_OBJ_WRITTEN(self, Qundef, fiber);
@@ -816,15 +838,29 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
816
838
  return storage;
817
839
  }
818
840
 
841
+ // Return true when it is safe and useful to enter the blocking selector wait.
819
842
  static
820
- int timeout_nonblocking(struct timespec * timespec) {
821
- return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
843
+ int select_blocking_allowed(struct timespec * timespec) {
844
+ // 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.
845
+ if (timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0) {
846
+ return 0;
847
+ }
848
+
849
+ #ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
850
+ // On Rubies without `RB_NOGVL_PENDING_INTERRUPT_FAIL`, `rb_thread_call_without_gvl2` can enter an indefinite native wait even if a masked interrupt is already pending for this thread. This is the last safe point to avoid that wait: we still hold the GVL, so the pending interrupt queue cannot change concurrently before we decide whether to enter the blocking path.
851
+ if (IO_Event_Selector_pending_interrupt()) {
852
+ return 0;
853
+ }
854
+ #endif
855
+
856
+ return 1;
822
857
  }
823
858
 
824
859
  struct select_arguments {
825
860
  struct IO_Event_Selector_KQueue *selector;
826
861
 
827
862
  int count;
863
+ int result;
828
864
  struct kevent events[KQUEUE_MAX_EVENTS];
829
865
 
830
866
  struct timespec storage;
@@ -837,38 +873,48 @@ static
837
873
  void * select_internal(void *_arguments) {
838
874
  struct select_arguments * arguments = (struct select_arguments *)_arguments;
839
875
 
840
- arguments->count = kevent(arguments->selector->descriptor, NULL, 0, arguments->events, arguments->count, arguments->timeout);
876
+ arguments->result = kevent(arguments->selector->descriptor, NULL, 0, arguments->events, arguments->count, arguments->timeout);
841
877
 
842
878
  return NULL;
843
879
  }
844
880
 
845
881
  static
846
- void select_internal_without_gvl(struct select_arguments *arguments) {
882
+ int select_internal_without_gvl(struct select_arguments *arguments) {
883
+ arguments->result = -1;
847
884
  arguments->selector->blocked = 1;
848
885
 
849
- rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
886
+ #ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
887
+ rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTERRUPT_FAIL);
888
+ #else
889
+ rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
890
+ #endif
850
891
  arguments->selector->blocked = 0;
851
892
 
852
- if (arguments->count == -1) {
853
- if (errno != EINTR) {
893
+ if (arguments->result == -1) {
894
+ // If Ruby skips the native callback, the result sentinel can remain `-1`; `errno` may be `0` or `EINTR` depending on the Ruby implementation. Both cases mean the blocking wait did not produce any events.
895
+ if (errno != EINTR && errno != 0) {
854
896
  rb_sys_fail("select_internal_without_gvl:kevent");
855
897
  } else {
856
- arguments->count = 0;
898
+ return 0;
857
899
  }
858
900
  }
901
+
902
+ return arguments->result;
859
903
  }
860
904
 
861
905
  static
862
- void select_internal_with_gvl(struct select_arguments *arguments) {
906
+ int select_internal_with_gvl(struct select_arguments *arguments) {
863
907
  select_internal((void *)arguments);
864
908
 
865
- if (arguments->count == -1) {
909
+ if (arguments->result == -1) {
866
910
  if (errno != EINTR) {
867
911
  rb_sys_fail("select_internal_with_gvl:kevent");
868
912
  } else {
869
- arguments->count = 0;
913
+ return 0;
870
914
  }
871
915
  }
916
+
917
+ return arguments->result;
872
918
  }
873
919
 
874
920
  static
@@ -922,14 +968,14 @@ VALUE select_handle_events(VALUE _arguments)
922
968
  struct select_arguments *arguments = (struct select_arguments *)_arguments;
923
969
  struct IO_Event_Selector_KQueue *selector = arguments->selector;
924
970
 
925
- for (int i = 0; i < arguments->count; i += 1) {
971
+ for (int i = 0; i < arguments->result; i += 1) {
926
972
  if (arguments->events[i].udata) {
927
973
  struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata;
928
974
  kqueue_descriptor->ready_events |= events_from_kevent_filter(arguments->events[i].filter);
929
975
  }
930
976
  }
931
977
 
932
- for (int i = 0; i < arguments->count; i += 1) {
978
+ for (int i = 0; i < arguments->result; i += 1) {
933
979
  if (arguments->events[i].udata) {
934
980
  struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata;
935
981
  IO_Event_Selector_KQueue_handle(selector, arguments->events[i].ident, kqueue_descriptor, &arguments->saved);
@@ -940,7 +986,7 @@ VALUE select_handle_events(VALUE _arguments)
940
986
  }
941
987
  }
942
988
 
943
- return RB_INT2NUM(arguments->count);
989
+ return RB_INT2NUM(arguments->result);
944
990
  }
945
991
 
946
992
  static
@@ -965,6 +1011,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
965
1011
  struct select_arguments arguments = {
966
1012
  .selector = selector,
967
1013
  .count = KQUEUE_MAX_EVENTS,
1014
+ .result = 0,
968
1015
  .storage = {
969
1016
  .tv_sec = 0,
970
1017
  .tv_nsec = 0
@@ -975,14 +1022,14 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
975
1022
  arguments.timeout = &arguments.storage;
976
1023
 
977
1024
  // We break this implementation into two parts.
978
- // (1) count = kevent(..., timeout = 0)
979
- // (2) without gvl: kevent(..., timeout = 0) if count == 0 and timeout != 0
1025
+ // (1) result = kevent(..., timeout = 0)
1026
+ // (2) without gvl: kevent(..., timeout = 0) if result == 0 and timeout != 0
980
1027
  // This allows us to avoid releasing and reacquiring the GVL.
981
1028
  // Non-comprehensive testing shows this gives a 1.5x speedup.
982
1029
 
983
1030
  // First do the syscall with no timeout to get any immediately available events:
984
1031
  if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl timeout=" IO_EVENT_TIME_PRINTF_TIMESPEC "\r\n", IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage));
985
- select_internal_with_gvl(&arguments);
1032
+ int result = select_internal_with_gvl(&arguments);
986
1033
  if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl done\r\n");
987
1034
 
988
1035
  // If we:
@@ -990,17 +1037,15 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
990
1037
  // 2. Didn't process any events from non-blocking select (above), and
991
1038
  // 3. There are no items in the ready list,
992
1039
  // then we can perform a blocking select.
993
- if (!ready && !arguments.count && !selector->backend.ready) {
1040
+ if (!ready && !result && !selector->backend.ready) {
994
1041
  arguments.timeout = make_timeout(duration, &arguments.storage);
995
1042
 
996
- if (!timeout_nonblocking(arguments.timeout)) {
997
- arguments.count = KQUEUE_MAX_EVENTS;
998
-
1043
+ if (select_blocking_allowed(arguments.timeout)) {
999
1044
  struct timespec start_time;
1000
1045
  IO_Event_Time_current(&start_time);
1001
1046
 
1002
1047
  if (DEBUG) fprintf(stderr, "IO_Event_Selector_KQueue_select timeout=" IO_EVENT_TIME_PRINTF_TIMESPEC "\n", IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage));
1003
- select_internal_without_gvl(&arguments);
1048
+ result = select_internal_without_gvl(&arguments);
1004
1049
 
1005
1050
  struct timespec end_time;
1006
1051
  IO_Event_Time_current(&end_time);
@@ -1008,7 +1053,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
1008
1053
  }
1009
1054
  }
1010
1055
 
1011
- if (arguments.count) {
1056
+ if (result) {
1012
1057
  return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments);
1013
1058
  } else {
1014
1059
  return RB_INT2NUM(0);
@@ -1090,6 +1135,7 @@ void Init_IO_Event_Selector_KQueue(VALUE IO_Event_Selector) {
1090
1135
  rb_define_method(IO_Event_Selector_KQueue, "select", IO_Event_Selector_KQueue_select, 1);
1091
1136
  rb_define_method(IO_Event_Selector_KQueue, "wakeup", IO_Event_Selector_KQueue_wakeup, 0);
1092
1137
  rb_define_method(IO_Event_Selector_KQueue, "close", IO_Event_Selector_KQueue_close, 0);
1138
+ rb_define_method(IO_Event_Selector_KQueue, "closed?", IO_Event_Selector_KQueue_closed_p, 0);
1093
1139
 
1094
1140
  rb_define_method(IO_Event_Selector_KQueue, "io_wait", IO_Event_Selector_KQueue_io_wait, 3);
1095
1141
 
@@ -8,6 +8,8 @@
8
8
 
9
9
  static const int DEBUG = 0;
10
10
 
11
+ ID IO_Event_Selector_pending_interrupt_p_id;
12
+
11
13
  #ifndef HAVE_RB_IO_DESCRIPTOR
12
14
  static ID id_fileno;
13
15
 
@@ -79,6 +81,8 @@ static VALUE IO_Event_Selector_nonblock(VALUE class, VALUE io)
79
81
  }
80
82
 
81
83
  void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
84
+ IO_Event_Selector_pending_interrupt_p_id = rb_intern("pending_interrupt?");
85
+
82
86
  #ifndef HAVE_RB_IO_DESCRIPTOR
83
87
  id_fileno = rb_intern("fileno");
84
88
  #endif
@@ -40,6 +40,12 @@ static inline int IO_Event_try_again(int error) {
40
40
  return error == EAGAIN || error == EWOULDBLOCK;
41
41
  }
42
42
 
43
+ extern ID IO_Event_Selector_pending_interrupt_p_id;
44
+
45
+ static inline int IO_Event_Selector_pending_interrupt(void) {
46
+ return RTEST(rb_funcall(rb_cThread, IO_Event_Selector_pending_interrupt_p_id, 0));
47
+ }
48
+
43
49
  #ifdef HAVE_RB_IO_DESCRIPTOR
44
50
  #define IO_Event_Selector_io_descriptor(io) rb_io_descriptor(io)
45
51
  #else
@@ -11,6 +11,7 @@
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
 
@@ -32,6 +33,7 @@ struct IO_Event_Selector_URing
32
33
  {
33
34
  struct IO_Event_Selector backend;
34
35
  struct io_uring ring;
36
+ pid_t owner;
35
37
 
36
38
  // Flag indicating whether the selector is currently blocked in a system call.
37
39
  // Set to 1 when blocked in io_uring_wait_cqe_timeout() without GVL, 0 otherwise.
@@ -115,14 +117,20 @@ void IO_Event_Selector_URing_Type_compact(void *_selector)
115
117
  static
116
118
  void close_internal(struct IO_Event_Selector_URing *selector)
117
119
  {
118
- if (selector->interrupt.descriptor >= 0) {
119
- IO_Event_Interrupt_close(&selector->interrupt);
120
+ if (selector->owner == getpid()) {
121
+ if (selector->interrupt.descriptor >= 0) {
122
+ IO_Event_Interrupt_close(&selector->interrupt);
123
+ selector->interrupt.descriptor = -1;
124
+ selector->wakeup_registered = 0;
125
+ }
126
+
127
+ if (selector->ring.ring_fd >= 0) {
128
+ io_uring_queue_exit(&selector->ring);
129
+ selector->ring.ring_fd = -1;
130
+ }
131
+ } else {
120
132
  selector->interrupt.descriptor = -1;
121
133
  selector->wakeup_registered = 0;
122
- }
123
-
124
- if (selector->ring.ring_fd >= 0) {
125
- io_uring_queue_exit(&selector->ring);
126
134
  selector->ring.ring_fd = -1;
127
135
  }
128
136
  }
@@ -237,6 +245,7 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) {
237
245
 
238
246
  IO_Event_Selector_initialize(&selector->backend, self, Qnil);
239
247
  selector->ring.ring_fd = -1;
248
+ selector->owner = 0;
240
249
 
241
250
  selector->blocked = 0;
242
251
  selector->interrupt.descriptor = -1;
@@ -299,6 +308,8 @@ VALUE IO_Event_Selector_URing_initialize(VALUE self, VALUE loop) {
299
308
  rb_syserr_fail(-result, "IO_Event_Selector_URing_initialize:io_uring_queue_init");
300
309
  }
301
310
 
311
+ selector->owner = getpid();
312
+
302
313
  rb_update_max_fd(selector->ring.ring_fd);
303
314
 
304
315
  // Interrupt for cross-thread wakeup: another thread calls signal(); the owner
@@ -339,6 +350,13 @@ VALUE IO_Event_Selector_URing_close(VALUE self) {
339
350
  return Qnil;
340
351
  }
341
352
 
353
+ VALUE IO_Event_Selector_URing_closed_p(VALUE self) {
354
+ struct IO_Event_Selector_URing *selector = NULL;
355
+ TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
356
+
357
+ return selector->ring.ring_fd < 0 || selector->owner != getpid() ? Qtrue : Qfalse;
358
+ }
359
+
342
360
  VALUE IO_Event_Selector_URing_transfer(VALUE self)
343
361
  {
344
362
  struct IO_Event_Selector_URing *selector = NULL;
@@ -757,8 +775,6 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
757
775
  struct IO_Event_Selector_URing *selector = NULL;
758
776
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
759
777
 
760
- int descriptor = IO_Event_Selector_io_descriptor(io);
761
-
762
778
  void *base;
763
779
  size_t size;
764
780
  rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
@@ -766,13 +782,17 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
766
782
  size_t length = NUM2SIZET(_length);
767
783
  size_t offset = NUM2SIZET(_offset);
768
784
  size_t total = 0;
769
- off_t from = io_seekable(descriptor);
770
785
 
771
786
  // 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
787
  if (offset > size) {
773
788
  return rb_fiber_scheduler_io_result(-1, EINVAL);
789
+ } else if (offset == size) {
790
+ return rb_fiber_scheduler_io_result(0, 0);
774
791
  }
775
792
 
793
+ int descriptor = IO_Event_Selector_io_descriptor(io);
794
+ off_t from = io_seekable(descriptor);
795
+
776
796
  size_t maximum_size = size - offset;
777
797
 
778
798
  // Are we performing a non-blocking read?
@@ -827,8 +847,6 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE
827
847
  struct IO_Event_Selector_URing *selector = NULL;
828
848
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
829
849
 
830
- int descriptor = IO_Event_Selector_io_descriptor(io);
831
-
832
850
  void *base;
833
851
  size_t size;
834
852
  rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
@@ -841,8 +859,12 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE
841
859
  // 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
860
  if (offset > size) {
843
861
  return rb_fiber_scheduler_io_result(-1, EINVAL);
862
+ } else if (offset == size) {
863
+ return rb_fiber_scheduler_io_result(0, 0);
844
864
  }
845
865
 
866
+ int descriptor = IO_Event_Selector_io_descriptor(io);
867
+
846
868
  size_t maximum_size = size - offset;
847
869
  while (maximum_size) {
848
870
  int result = io_read(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -945,8 +967,6 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
945
967
  struct IO_Event_Selector_URing *selector = NULL;
946
968
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
947
969
 
948
- int descriptor = IO_Event_Selector_io_descriptor(io);
949
-
950
970
  const void *base;
951
971
  size_t size;
952
972
  rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
@@ -954,7 +974,6 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
954
974
  size_t length = NUM2SIZET(_length);
955
975
  size_t offset = NUM2SIZET(_offset);
956
976
  size_t total = 0;
957
- off_t from = io_seekable(descriptor);
958
977
 
959
978
  if (length > size) {
960
979
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
@@ -963,8 +982,13 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
963
982
  // 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
983
  if (offset > size) {
965
984
  return rb_fiber_scheduler_io_result(-1, EINVAL);
985
+ } else if (offset == size) {
986
+ return rb_fiber_scheduler_io_result(0, 0);
966
987
  }
967
-
988
+
989
+ int descriptor = IO_Event_Selector_io_descriptor(io);
990
+ off_t from = io_seekable(descriptor);
991
+
968
992
  size_t maximum_size = size - offset;
969
993
  while (maximum_size) {
970
994
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -1005,8 +1029,6 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
1005
1029
  struct IO_Event_Selector_URing *selector = NULL;
1006
1030
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
1007
1031
 
1008
- int descriptor = IO_Event_Selector_io_descriptor(io);
1009
-
1010
1032
  const void *base;
1011
1033
  size_t size;
1012
1034
  rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
@@ -1023,8 +1045,12 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
1023
1045
  // 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
1046
  if (offset > size) {
1025
1047
  return rb_fiber_scheduler_io_result(-1, EINVAL);
1048
+ } else if (offset == size) {
1049
+ return rb_fiber_scheduler_io_result(0, 0);
1026
1050
  }
1027
-
1051
+
1052
+ int descriptor = IO_Event_Selector_io_descriptor(io);
1053
+
1028
1054
  size_t maximum_size = size - offset;
1029
1055
  while (maximum_size) {
1030
1056
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -1104,9 +1130,22 @@ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec
1104
1130
  return storage;
1105
1131
  }
1106
1132
 
1133
+ // Return true when it is safe and useful to enter the blocking selector wait.
1107
1134
  static
1108
- int timeout_nonblocking(struct __kernel_timespec *timespec) {
1109
- return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
1135
+ int select_blocking_allowed(struct __kernel_timespec *timespec) {
1136
+ // 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.
1137
+ if (timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0) {
1138
+ return 0;
1139
+ }
1140
+
1141
+ #ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
1142
+ // On Rubies without `RB_NOGVL_PENDING_INTERRUPT_FAIL`, `rb_thread_call_without_gvl2` can enter an indefinite native wait even if a masked interrupt is already pending for this thread. This is the last safe point to avoid that wait: we still hold the GVL, so the pending interrupt queue cannot change concurrently before we decide whether to enter the blocking path.
1143
+ if (IO_Event_Selector_pending_interrupt()) {
1144
+ return 0;
1145
+ }
1146
+ #endif
1147
+
1148
+ return 1;
1110
1149
  }
1111
1150
 
1112
1151
  struct select_arguments {
@@ -1145,22 +1184,27 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
1145
1184
 
1146
1185
  io_uring_submit_flush(selector);
1147
1186
 
1187
+ arguments->result = -EINTR;
1148
1188
  selector->blocked = 1;
1149
- rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
1189
+ #ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
1190
+ rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTERRUPT_FAIL);
1191
+ #else
1192
+ rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
1193
+ #endif
1150
1194
  selector->blocked = 0;
1151
1195
 
1152
1196
  if (arguments->result == -ETIME) {
1153
- arguments->result = 0;
1197
+ return 0;
1154
1198
  } else if (arguments->result == -EINTR) {
1155
- arguments->result = 0;
1199
+ return 0;
1156
1200
  } else if (arguments->result < 0) {
1157
1201
  rb_syserr_fail(-arguments->result, "select_internal_without_gvl:io_uring_wait_cqe_timeout");
1158
1202
  } else {
1159
1203
  // At least 1 event is waiting:
1160
- arguments->result = 1;
1204
+ return 1;
1161
1205
  }
1162
1206
 
1163
- return arguments->result;
1207
+ return 0;
1164
1208
  }
1165
1209
 
1166
1210
  static inline
@@ -1257,28 +1301,29 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
1257
1301
 
1258
1302
  int ready = IO_Event_Selector_ready_flush(&selector->backend);
1259
1303
 
1260
- int result = select_process_completions(selector);
1304
+ int completed = select_process_completions(selector);
1261
1305
 
1262
1306
  // If we:
1263
1307
  // 1. Didn't process any ready fibers, and
1264
1308
  // 2. Didn't process any events from non-blocking select (above), and
1265
1309
  // 3. There are no items in the ready list,
1266
1310
  // then we can perform a blocking select.
1267
- if (!ready && !result && !selector->backend.ready) {
1311
+ if (!ready && !completed && !selector->backend.ready) {
1268
1312
  // We might need to wait for events:
1269
1313
  struct select_arguments arguments = {
1270
1314
  .selector = selector,
1315
+ .result = 0,
1271
1316
  .timeout = NULL,
1272
1317
  };
1273
1318
 
1274
1319
  arguments.timeout = make_timeout(duration, &arguments.storage);
1275
1320
 
1276
- if (!selector->backend.ready && !timeout_nonblocking(arguments.timeout)) {
1321
+ if (!selector->backend.ready && select_blocking_allowed(arguments.timeout)) {
1277
1322
  struct timespec start_time;
1278
1323
  IO_Event_Time_current(&start_time);
1279
1324
 
1280
1325
  // This is a blocking operation, we wait for events:
1281
- result = select_internal_without_gvl(&arguments);
1326
+ int result = select_internal_without_gvl(&arguments);
1282
1327
 
1283
1328
  struct timespec end_time;
1284
1329
  IO_Event_Time_current(&end_time);
@@ -1286,12 +1331,12 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
1286
1331
 
1287
1332
  // After waiting/flushing the SQ, check if there are any completions:
1288
1333
  if (result > 0) {
1289
- result = select_process_completions(selector);
1334
+ completed = select_process_completions(selector);
1290
1335
  }
1291
1336
  }
1292
1337
  }
1293
1338
 
1294
- return RB_INT2NUM(result);
1339
+ return RB_INT2NUM(completed);
1295
1340
  }
1296
1341
 
1297
1342
  VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
@@ -1370,6 +1415,7 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
1370
1415
  rb_define_method(IO_Event_Selector_URing, "select", IO_Event_Selector_URing_select, 1);
1371
1416
  rb_define_method(IO_Event_Selector_URing, "wakeup", IO_Event_Selector_URing_wakeup, 0);
1372
1417
  rb_define_method(IO_Event_Selector_URing, "close", IO_Event_Selector_URing_close, 0);
1418
+ rb_define_method(IO_Event_Selector_URing, "closed?", IO_Event_Selector_URing_closed_p, 0);
1373
1419
 
1374
1420
  rb_define_method(IO_Event_Selector_URing, "io_wait", IO_Event_Selector_URing_io_wait, 3);
1375
1421
 
@@ -111,6 +111,11 @@ module IO::Event
111
111
  @log&.flush
112
112
  end
113
113
 
114
+ # @returns [Boolean] Whether the wrapped selector is closed.
115
+ def closed?
116
+ @selector.nil? || @selector.closed?
117
+ end
118
+
114
119
  # Transfer from the calling fiber to the selector.
115
120
  def transfer
116
121
  log("Transfering to event loop")
@@ -13,6 +13,7 @@ module IO::Event
13
13
  # Initialize the selector with the given event loop fiber.
14
14
  def initialize(loop)
15
15
  @loop = loop
16
+ @owner = Process.pid
16
17
 
17
18
  @waiting = Hash.new.compare_by_identity
18
19
 
@@ -52,6 +53,11 @@ module IO::Event
52
53
  @waiting = nil
53
54
  end
54
55
 
56
+ # @returns [Boolean] Whether the selector is closed or belongs to a different process.
57
+ def closed?
58
+ @loop.nil? || @owner != Process.pid
59
+ end
60
+
55
61
  # An optional reference to a fiber which can be cleared before it is resumed.
56
62
  Optional = Struct.new(:fiber) do
57
63
  # Transfer control to the fiber if it is still available.
@@ -199,6 +205,8 @@ module IO::Event
199
205
  # Ensure offset is within the bounds of the buffer to avoid ArgumentError
200
206
  if offset > buffer.size
201
207
  return -Errno::EINVAL::Errno
208
+ elsif offset == buffer.size
209
+ return 0
202
210
  end
203
211
 
204
212
  total = 0
@@ -234,6 +242,8 @@ module IO::Event
234
242
  # Ensure offset is within the bounds of the buffer to avoid ArgumentError
235
243
  if offset > buffer.size
236
244
  return -Errno::EINVAL::Errno
245
+ elsif offset == buffer.size
246
+ return 0
237
247
  end
238
248
 
239
249
  total = 0
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.16.4"
10
+ VERSION = "1.18.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -18,6 +18,14 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
18
18
 
19
19
  Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
20
 
21
+ ### v1.18.0
22
+
23
+ - **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
24
+
25
+ ### v1.17.0
26
+
27
+ - Report inherited selector objects as closed after fork, and avoid closing descriptors they no longer own.
28
+
21
29
  ### v1.16.4
22
30
 
23
31
  - Correctly implement `Interrupt#signal` so that it is robust enough to be called by `Scheduler#unblock`.
@@ -56,14 +64,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
56
64
 
57
65
  - Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.
58
66
 
59
- ### v1.14.3
60
-
61
- - Fix several implementation bugs that could cause deadlocks on blocking writes.
62
-
63
- ### v1.14.0
64
-
65
- - [Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods](https://socketry.github.io/io-event/releases/index#enhanced-io::event::priorityheap-with-deletion-and-bulk-insertion-methods)
66
-
67
67
  ## Contributing
68
68
 
69
69
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v1.18.0
4
+
5
+ - **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
6
+
7
+ ## v1.17.0
8
+
9
+ - Report inherited selector objects as closed after fork, and avoid closing descriptors they no longer own.
10
+
3
11
  ## v1.16.4
4
12
 
5
13
  - Correctly implement `Interrupt#signal` so that it is robust enough to be called by `Scheduler#unblock`.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.16.4
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file