io-event 1.17.0 → 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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/ext/io/event/selector/epoll.c +52 -21
- data/ext/io/event/selector/kqueue.c +46 -23
- data/ext/io/event/selector/selector.c +4 -0
- data/ext/io/event/selector/selector.h +6 -0
- data/ext/io/event/selector/uring.c +32 -13
- data/lib/io/event/version.rb +1 -1
- data/readme.md +4 -4
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- metadata.gz.sig +1 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ad9ab9326ef0bb4d637833e5d9705940a7d150c0e42154ee4642fbf6f4b4b49
|
|
4
|
+
data.tar.gz: bb1274afaa893df427b3eb422aa5c198da08493126e8b5f3ee2daac375f1ebda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 543bdb171056e37849b7042c0d822e7421908bbd006227be92b1b2c5c12d7988c8201ce02316c9f0aa32df9746f4a6d88345c8eb9f38f9c5018f4adbaeca27b9
|
|
7
|
+
data.tar.gz: 65b3f2b61cf38b3c66cb2d0e9299ece198e546d0e30676f8bbd616fdccb043b47f032f0e8fd00bb5bf14bdef471f5ebb1c4769e8de86654900916fc8952c2be7
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -830,14 +830,33 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
|
|
|
830
830
|
}
|
|
831
831
|
|
|
832
832
|
static
|
|
833
|
-
int
|
|
833
|
+
int timeout_is_nonblocking(struct timespec * timespec) {
|
|
834
834
|
return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
|
|
835
835
|
}
|
|
836
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
|
+
|
|
837
855
|
struct select_arguments {
|
|
838
856
|
struct IO_Event_Selector_EPoll *selector;
|
|
839
857
|
|
|
840
858
|
int count;
|
|
859
|
+
int result;
|
|
841
860
|
struct epoll_event events[EPOLL_MAX_EVENTS];
|
|
842
861
|
|
|
843
862
|
struct timespec * timeout;
|
|
@@ -851,7 +870,7 @@ static int make_timeout_ms(struct timespec * timeout) {
|
|
|
851
870
|
return -1;
|
|
852
871
|
}
|
|
853
872
|
|
|
854
|
-
if (
|
|
873
|
+
if (timeout_is_nonblocking(timeout)) {
|
|
855
874
|
return 0;
|
|
856
875
|
}
|
|
857
876
|
|
|
@@ -872,13 +891,13 @@ void * select_internal(void *_arguments) {
|
|
|
872
891
|
struct select_arguments * arguments = (struct select_arguments *)_arguments;
|
|
873
892
|
|
|
874
893
|
#if defined(HAVE_EPOLL_PWAIT2)
|
|
875
|
-
arguments->
|
|
894
|
+
arguments->result = epoll_pwait2(arguments->selector->descriptor, arguments->events, arguments->count, arguments->timeout, NULL);
|
|
876
895
|
|
|
877
896
|
// Comment out the above line and enable the below lines to test ENOSYS code path.
|
|
878
|
-
// arguments->
|
|
897
|
+
// arguments->result = -1;
|
|
879
898
|
// errno = ENOSYS;
|
|
880
899
|
|
|
881
|
-
if (!enosys_error(arguments->
|
|
900
|
+
if (!enosys_error(arguments->result)) {
|
|
882
901
|
return NULL;
|
|
883
902
|
}
|
|
884
903
|
else {
|
|
@@ -886,37 +905,47 @@ void * select_internal(void *_arguments) {
|
|
|
886
905
|
}
|
|
887
906
|
#endif
|
|
888
907
|
|
|
889
|
-
arguments->
|
|
908
|
+
arguments->result = epoll_wait(arguments->selector->descriptor, arguments->events, arguments->count, make_timeout_ms(arguments->timeout));
|
|
890
909
|
|
|
891
910
|
return NULL;
|
|
892
911
|
}
|
|
893
912
|
|
|
894
913
|
static
|
|
895
|
-
|
|
914
|
+
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
915
|
+
arguments->result = -1;
|
|
896
916
|
arguments->selector->blocked = 1;
|
|
897
|
-
|
|
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
|
|
898
922
|
arguments->selector->blocked = 0;
|
|
899
923
|
|
|
900
|
-
if (arguments->
|
|
901
|
-
|
|
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) {
|
|
902
927
|
rb_sys_fail("select_internal_without_gvl:epoll_wait");
|
|
903
928
|
} else {
|
|
904
|
-
|
|
929
|
+
return 0;
|
|
905
930
|
}
|
|
906
931
|
}
|
|
932
|
+
|
|
933
|
+
return arguments->result;
|
|
907
934
|
}
|
|
908
935
|
|
|
909
936
|
static
|
|
910
|
-
|
|
937
|
+
int select_internal_with_gvl(struct select_arguments *arguments) {
|
|
911
938
|
select_internal((void *)arguments);
|
|
912
939
|
|
|
913
|
-
if (arguments->
|
|
940
|
+
if (arguments->result == -1) {
|
|
914
941
|
if (errno != EINTR) {
|
|
915
942
|
rb_sys_fail("select_internal_with_gvl:epoll_wait");
|
|
916
943
|
} else {
|
|
917
|
-
|
|
944
|
+
return 0;
|
|
918
945
|
}
|
|
919
946
|
}
|
|
947
|
+
|
|
948
|
+
return arguments->result;
|
|
920
949
|
}
|
|
921
950
|
|
|
922
951
|
static
|
|
@@ -972,7 +1001,7 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
972
1001
|
struct select_arguments *arguments = (struct select_arguments *)_arguments;
|
|
973
1002
|
struct IO_Event_Selector_EPoll *selector = arguments->selector;
|
|
974
1003
|
|
|
975
|
-
for (int i = 0; i < arguments->
|
|
1004
|
+
for (int i = 0; i < arguments->result; i += 1) {
|
|
976
1005
|
const struct epoll_event *event = &arguments->events[i];
|
|
977
1006
|
if (DEBUG) fprintf(stderr, "-> fd=%d events=%d\n", event->data.fd, event->events);
|
|
978
1007
|
|
|
@@ -983,7 +1012,7 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
983
1012
|
}
|
|
984
1013
|
}
|
|
985
1014
|
|
|
986
|
-
return INT2NUM(arguments->
|
|
1015
|
+
return INT2NUM(arguments->result);
|
|
987
1016
|
}
|
|
988
1017
|
|
|
989
1018
|
static
|
|
@@ -1008,6 +1037,8 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1008
1037
|
|
|
1009
1038
|
struct select_arguments arguments = {
|
|
1010
1039
|
.selector = selector,
|
|
1040
|
+
.count = EPOLL_MAX_EVENTS,
|
|
1041
|
+
.result = 0,
|
|
1011
1042
|
.storage = {
|
|
1012
1043
|
.tv_sec = 0,
|
|
1013
1044
|
.tv_nsec = 0
|
|
@@ -1018,22 +1049,22 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1018
1049
|
arguments.timeout = &arguments.storage;
|
|
1019
1050
|
|
|
1020
1051
|
// Process any currently pending events:
|
|
1021
|
-
select_internal_with_gvl(&arguments);
|
|
1052
|
+
int result = select_internal_with_gvl(&arguments);
|
|
1022
1053
|
|
|
1023
1054
|
// If we:
|
|
1024
1055
|
// 1. Didn't process any ready fibers, and
|
|
1025
1056
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1026
1057
|
// 3. There are no items in the ready list,
|
|
1027
1058
|
// then we can perform a blocking select.
|
|
1028
|
-
if (!ready && !
|
|
1059
|
+
if (!ready && !result && !selector->backend.ready) {
|
|
1029
1060
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1030
1061
|
|
|
1031
|
-
if (
|
|
1062
|
+
if (select_blocking_allowed(arguments.timeout)) {
|
|
1032
1063
|
struct timespec start_time;
|
|
1033
1064
|
IO_Event_Time_current(&start_time);
|
|
1034
1065
|
|
|
1035
1066
|
// Wait for events to occur:
|
|
1036
|
-
select_internal_without_gvl(&arguments);
|
|
1067
|
+
result = select_internal_without_gvl(&arguments);
|
|
1037
1068
|
|
|
1038
1069
|
struct timespec end_time;
|
|
1039
1070
|
IO_Event_Time_current(&end_time);
|
|
@@ -1041,7 +1072,7 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1041
1072
|
}
|
|
1042
1073
|
}
|
|
1043
1074
|
|
|
1044
|
-
if (
|
|
1075
|
+
if (result) {
|
|
1045
1076
|
return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments);
|
|
1046
1077
|
} else {
|
|
1047
1078
|
return RB_INT2NUM(0);
|
|
@@ -838,15 +838,29 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
|
|
|
838
838
|
return storage;
|
|
839
839
|
}
|
|
840
840
|
|
|
841
|
+
// Return true when it is safe and useful to enter the blocking selector wait.
|
|
841
842
|
static
|
|
842
|
-
int
|
|
843
|
-
|
|
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;
|
|
844
857
|
}
|
|
845
858
|
|
|
846
859
|
struct select_arguments {
|
|
847
860
|
struct IO_Event_Selector_KQueue *selector;
|
|
848
861
|
|
|
849
862
|
int count;
|
|
863
|
+
int result;
|
|
850
864
|
struct kevent events[KQUEUE_MAX_EVENTS];
|
|
851
865
|
|
|
852
866
|
struct timespec storage;
|
|
@@ -859,38 +873,48 @@ static
|
|
|
859
873
|
void * select_internal(void *_arguments) {
|
|
860
874
|
struct select_arguments * arguments = (struct select_arguments *)_arguments;
|
|
861
875
|
|
|
862
|
-
arguments->
|
|
876
|
+
arguments->result = kevent(arguments->selector->descriptor, NULL, 0, arguments->events, arguments->count, arguments->timeout);
|
|
863
877
|
|
|
864
878
|
return NULL;
|
|
865
879
|
}
|
|
866
880
|
|
|
867
881
|
static
|
|
868
|
-
|
|
882
|
+
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
883
|
+
arguments->result = -1;
|
|
869
884
|
arguments->selector->blocked = 1;
|
|
870
885
|
|
|
871
|
-
|
|
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
|
|
872
891
|
arguments->selector->blocked = 0;
|
|
873
892
|
|
|
874
|
-
if (arguments->
|
|
875
|
-
|
|
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) {
|
|
876
896
|
rb_sys_fail("select_internal_without_gvl:kevent");
|
|
877
897
|
} else {
|
|
878
|
-
|
|
898
|
+
return 0;
|
|
879
899
|
}
|
|
880
900
|
}
|
|
901
|
+
|
|
902
|
+
return arguments->result;
|
|
881
903
|
}
|
|
882
904
|
|
|
883
905
|
static
|
|
884
|
-
|
|
906
|
+
int select_internal_with_gvl(struct select_arguments *arguments) {
|
|
885
907
|
select_internal((void *)arguments);
|
|
886
908
|
|
|
887
|
-
if (arguments->
|
|
909
|
+
if (arguments->result == -1) {
|
|
888
910
|
if (errno != EINTR) {
|
|
889
911
|
rb_sys_fail("select_internal_with_gvl:kevent");
|
|
890
912
|
} else {
|
|
891
|
-
|
|
913
|
+
return 0;
|
|
892
914
|
}
|
|
893
915
|
}
|
|
916
|
+
|
|
917
|
+
return arguments->result;
|
|
894
918
|
}
|
|
895
919
|
|
|
896
920
|
static
|
|
@@ -944,14 +968,14 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
944
968
|
struct select_arguments *arguments = (struct select_arguments *)_arguments;
|
|
945
969
|
struct IO_Event_Selector_KQueue *selector = arguments->selector;
|
|
946
970
|
|
|
947
|
-
for (int i = 0; i < arguments->
|
|
971
|
+
for (int i = 0; i < arguments->result; i += 1) {
|
|
948
972
|
if (arguments->events[i].udata) {
|
|
949
973
|
struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata;
|
|
950
974
|
kqueue_descriptor->ready_events |= events_from_kevent_filter(arguments->events[i].filter);
|
|
951
975
|
}
|
|
952
976
|
}
|
|
953
977
|
|
|
954
|
-
for (int i = 0; i < arguments->
|
|
978
|
+
for (int i = 0; i < arguments->result; i += 1) {
|
|
955
979
|
if (arguments->events[i].udata) {
|
|
956
980
|
struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata;
|
|
957
981
|
IO_Event_Selector_KQueue_handle(selector, arguments->events[i].ident, kqueue_descriptor, &arguments->saved);
|
|
@@ -962,7 +986,7 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
962
986
|
}
|
|
963
987
|
}
|
|
964
988
|
|
|
965
|
-
return RB_INT2NUM(arguments->
|
|
989
|
+
return RB_INT2NUM(arguments->result);
|
|
966
990
|
}
|
|
967
991
|
|
|
968
992
|
static
|
|
@@ -987,6 +1011,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
987
1011
|
struct select_arguments arguments = {
|
|
988
1012
|
.selector = selector,
|
|
989
1013
|
.count = KQUEUE_MAX_EVENTS,
|
|
1014
|
+
.result = 0,
|
|
990
1015
|
.storage = {
|
|
991
1016
|
.tv_sec = 0,
|
|
992
1017
|
.tv_nsec = 0
|
|
@@ -997,14 +1022,14 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
997
1022
|
arguments.timeout = &arguments.storage;
|
|
998
1023
|
|
|
999
1024
|
// We break this implementation into two parts.
|
|
1000
|
-
// (1)
|
|
1001
|
-
// (2) without gvl: kevent(..., timeout = 0) if
|
|
1025
|
+
// (1) result = kevent(..., timeout = 0)
|
|
1026
|
+
// (2) without gvl: kevent(..., timeout = 0) if result == 0 and timeout != 0
|
|
1002
1027
|
// This allows us to avoid releasing and reacquiring the GVL.
|
|
1003
1028
|
// Non-comprehensive testing shows this gives a 1.5x speedup.
|
|
1004
1029
|
|
|
1005
1030
|
// First do the syscall with no timeout to get any immediately available events:
|
|
1006
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));
|
|
1007
|
-
select_internal_with_gvl(&arguments);
|
|
1032
|
+
int result = select_internal_with_gvl(&arguments);
|
|
1008
1033
|
if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl done\r\n");
|
|
1009
1034
|
|
|
1010
1035
|
// If we:
|
|
@@ -1012,17 +1037,15 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
1012
1037
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1013
1038
|
// 3. There are no items in the ready list,
|
|
1014
1039
|
// then we can perform a blocking select.
|
|
1015
|
-
if (!ready && !
|
|
1040
|
+
if (!ready && !result && !selector->backend.ready) {
|
|
1016
1041
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1017
1042
|
|
|
1018
|
-
if (
|
|
1019
|
-
arguments.count = KQUEUE_MAX_EVENTS;
|
|
1020
|
-
|
|
1043
|
+
if (select_blocking_allowed(arguments.timeout)) {
|
|
1021
1044
|
struct timespec start_time;
|
|
1022
1045
|
IO_Event_Time_current(&start_time);
|
|
1023
1046
|
|
|
1024
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));
|
|
1025
|
-
select_internal_without_gvl(&arguments);
|
|
1048
|
+
result = select_internal_without_gvl(&arguments);
|
|
1026
1049
|
|
|
1027
1050
|
struct timespec end_time;
|
|
1028
1051
|
IO_Event_Time_current(&end_time);
|
|
@@ -1030,7 +1053,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
1030
1053
|
}
|
|
1031
1054
|
}
|
|
1032
1055
|
|
|
1033
|
-
if (
|
|
1056
|
+
if (result) {
|
|
1034
1057
|
return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments);
|
|
1035
1058
|
} else {
|
|
1036
1059
|
return RB_INT2NUM(0);
|
|
@@ -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
|
|
@@ -1130,9 +1130,22 @@ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec
|
|
|
1130
1130
|
return storage;
|
|
1131
1131
|
}
|
|
1132
1132
|
|
|
1133
|
+
// Return true when it is safe and useful to enter the blocking selector wait.
|
|
1133
1134
|
static
|
|
1134
|
-
int
|
|
1135
|
-
|
|
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;
|
|
1136
1149
|
}
|
|
1137
1150
|
|
|
1138
1151
|
struct select_arguments {
|
|
@@ -1171,22 +1184,27 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
|
1171
1184
|
|
|
1172
1185
|
io_uring_submit_flush(selector);
|
|
1173
1186
|
|
|
1187
|
+
arguments->result = -EINTR;
|
|
1174
1188
|
selector->blocked = 1;
|
|
1175
|
-
|
|
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
|
|
1176
1194
|
selector->blocked = 0;
|
|
1177
1195
|
|
|
1178
1196
|
if (arguments->result == -ETIME) {
|
|
1179
|
-
|
|
1197
|
+
return 0;
|
|
1180
1198
|
} else if (arguments->result == -EINTR) {
|
|
1181
|
-
|
|
1199
|
+
return 0;
|
|
1182
1200
|
} else if (arguments->result < 0) {
|
|
1183
1201
|
rb_syserr_fail(-arguments->result, "select_internal_without_gvl:io_uring_wait_cqe_timeout");
|
|
1184
1202
|
} else {
|
|
1185
1203
|
// At least 1 event is waiting:
|
|
1186
|
-
|
|
1204
|
+
return 1;
|
|
1187
1205
|
}
|
|
1188
1206
|
|
|
1189
|
-
return
|
|
1207
|
+
return 0;
|
|
1190
1208
|
}
|
|
1191
1209
|
|
|
1192
1210
|
static inline
|
|
@@ -1283,28 +1301,29 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
|
|
|
1283
1301
|
|
|
1284
1302
|
int ready = IO_Event_Selector_ready_flush(&selector->backend);
|
|
1285
1303
|
|
|
1286
|
-
int
|
|
1304
|
+
int completed = select_process_completions(selector);
|
|
1287
1305
|
|
|
1288
1306
|
// If we:
|
|
1289
1307
|
// 1. Didn't process any ready fibers, and
|
|
1290
1308
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1291
1309
|
// 3. There are no items in the ready list,
|
|
1292
1310
|
// then we can perform a blocking select.
|
|
1293
|
-
if (!ready && !
|
|
1311
|
+
if (!ready && !completed && !selector->backend.ready) {
|
|
1294
1312
|
// We might need to wait for events:
|
|
1295
1313
|
struct select_arguments arguments = {
|
|
1296
1314
|
.selector = selector,
|
|
1315
|
+
.result = 0,
|
|
1297
1316
|
.timeout = NULL,
|
|
1298
1317
|
};
|
|
1299
1318
|
|
|
1300
1319
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1301
1320
|
|
|
1302
|
-
if (!selector->backend.ready &&
|
|
1321
|
+
if (!selector->backend.ready && select_blocking_allowed(arguments.timeout)) {
|
|
1303
1322
|
struct timespec start_time;
|
|
1304
1323
|
IO_Event_Time_current(&start_time);
|
|
1305
1324
|
|
|
1306
1325
|
// This is a blocking operation, we wait for events:
|
|
1307
|
-
result = select_internal_without_gvl(&arguments);
|
|
1326
|
+
int result = select_internal_without_gvl(&arguments);
|
|
1308
1327
|
|
|
1309
1328
|
struct timespec end_time;
|
|
1310
1329
|
IO_Event_Time_current(&end_time);
|
|
@@ -1312,12 +1331,12 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
|
|
|
1312
1331
|
|
|
1313
1332
|
// After waiting/flushing the SQ, check if there are any completions:
|
|
1314
1333
|
if (result > 0) {
|
|
1315
|
-
|
|
1334
|
+
completed = select_process_completions(selector);
|
|
1316
1335
|
}
|
|
1317
1336
|
}
|
|
1318
1337
|
}
|
|
1319
1338
|
|
|
1320
|
-
return RB_INT2NUM(
|
|
1339
|
+
return RB_INT2NUM(completed);
|
|
1321
1340
|
}
|
|
1322
1341
|
|
|
1323
1342
|
VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
|
data/lib/io/event/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -18,6 +18,10 @@ 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
|
+
|
|
21
25
|
### v1.17.0
|
|
22
26
|
|
|
23
27
|
- Report inherited selector objects as closed after fork, and avoid closing descriptors they no longer own.
|
|
@@ -60,10 +64,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
60
64
|
|
|
61
65
|
- Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.
|
|
62
66
|
|
|
63
|
-
### v1.14.3
|
|
64
|
-
|
|
65
|
-
- Fix several implementation bugs that could cause deadlocks on blocking writes.
|
|
66
|
-
|
|
67
67
|
## Contributing
|
|
68
68
|
|
|
69
69
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
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
|
+
|
|
3
7
|
## v1.17.0
|
|
4
8
|
|
|
5
9
|
- Report inherited selector objects as closed after fork, and avoid closing descriptors they no longer own.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
D�mC[ Pf�Q��F�u�Ƥ2bA�E���Oӕ�ف�Y|�z"d������0dm��ͿR:�v�ճ�'���bNc�
|
|
1
|
+
Z�`.a��&����r�c���8��C|�)ex�U�@��FzP~ �]�/��E��W�Ōl�RR�8G�-; �^�F<���^�vo2o�%x����5<fl'�9��Q���V[e9{V Z��l�C�����c���iK�8~��JsC�M=!�J8�|�L���� ���q۔�ۯ@`��gذY�B5㿂ʢ�G�Rm7���%f� ���?�R.�f9��&i���]���e����]�6�bJ�p�k_�`z�����ʷl��KWf!f�W���s�{r�����x~�jN�Q'+N������d���;3Q�������Ph������>�pa �"d�D�1�iV�Cy��kC����MM�j��%&�8��������s�$@��
|