io-event 1.17.0 → 1.19.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/extconf.rb +2 -3
- data/ext/io/event/selector/epoll.c +59 -23
- data/ext/io/event/selector/kqueue.c +53 -25
- data/ext/io/event/selector/selector.c +17 -1
- data/ext/io/event/selector/selector.h +16 -2
- data/ext/io/event/selector/uring.c +107 -17
- data/lib/io/event/interrupt.rb +1 -1
- data/lib/io/event/selector/select.rb +1 -3
- data/lib/io/event/selector.rb +18 -0
- data/lib/io/event/version.rb +1 -1
- data/license.md +1 -0
- data/readme.md +9 -8
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +2 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f4e37070c965d89037b3fac95c4b5de378273050ee28eb397a954c5fce84a6ae
|
|
4
|
+
data.tar.gz: 7571ee0edd25008cafe5cc9a81488969d5d269ec1e53793fa7cca393bbfd754c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22cb0db38d55f3710fc36469c2f2e1162fd69c69c228a9be2c4310f83dbc5304c8cb4194fc7fe2ca4b4316f906ff17341bcceeb24ed1cf9ebe70cd076bb05ca2
|
|
7
|
+
data.tar.gz: d7e4a07d6acf309898b3df66e8e6b2b21f09af99a7f21ac03f30c68ad32024a1766320159f350c450df18aa46c911951c87c6f38abd06c55c9635f7eb1c86831
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/ext/extconf.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# Copyright, 2023, by Math Ieu.
|
|
7
7
|
# Copyright, 2025, by Stanislav (Stas) Katkov.
|
|
8
8
|
# Copyright, 2026, by Stan Hu.
|
|
9
|
+
# Copyright, 2026, by Sharon Rosner.
|
|
9
10
|
|
|
10
11
|
return if RUBY_DESCRIPTION =~ /jruby/
|
|
11
12
|
|
|
@@ -32,9 +33,7 @@ have_func("rb_ext_ractor_safe")
|
|
|
32
33
|
have_func("&rb_fiber_transfer")
|
|
33
34
|
|
|
34
35
|
if have_library("uring") and have_header("liburing.h")
|
|
35
|
-
|
|
36
|
-
# have_func("io_uring_submit_and_wait_timeout", "liburing.h")
|
|
37
|
-
|
|
36
|
+
have_func("io_uring_prep_waitid", "liburing.h")
|
|
38
37
|
$srcs << "io/event/selector/uring.c"
|
|
39
38
|
end
|
|
40
39
|
|
|
@@ -462,7 +462,7 @@ VALUE process_wait_transfer(VALUE _arguments) {
|
|
|
462
462
|
IO_Event_Selector_loop_yield(&arguments->selector->backend);
|
|
463
463
|
|
|
464
464
|
if (arguments->waiting->ready) {
|
|
465
|
-
return
|
|
465
|
+
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
466
466
|
} else {
|
|
467
467
|
return Qfalse;
|
|
468
468
|
}
|
|
@@ -488,6 +488,11 @@ VALUE IO_Event_Selector_EPoll_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
488
488
|
pid_t pid = NUM2PIDT(_pid);
|
|
489
489
|
int flags = NUM2INT(_flags);
|
|
490
490
|
|
|
491
|
+
// `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:
|
|
492
|
+
if (pid <= 0) {
|
|
493
|
+
return IO_Event_Selector_process_wait(pid, flags);
|
|
494
|
+
}
|
|
495
|
+
|
|
491
496
|
int descriptor = pidfd_open(pid, 0);
|
|
492
497
|
|
|
493
498
|
if (descriptor == -1) {
|
|
@@ -497,7 +502,7 @@ VALUE IO_Event_Selector_EPoll_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
497
502
|
rb_update_max_fd(descriptor);
|
|
498
503
|
|
|
499
504
|
// `pidfd_open` (above) may be edge triggered, so we need to check if the process is already exited, and if so, return immediately, otherwise we will block indefinitely.
|
|
500
|
-
VALUE status =
|
|
505
|
+
VALUE status = IO_Event_Selector_process_status_reap(pid, flags);
|
|
501
506
|
if (status != Qnil) {
|
|
502
507
|
close(descriptor);
|
|
503
508
|
return status;
|
|
@@ -830,14 +835,33 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
|
|
|
830
835
|
}
|
|
831
836
|
|
|
832
837
|
static
|
|
833
|
-
int
|
|
838
|
+
int timeout_is_nonblocking(struct timespec * timespec) {
|
|
834
839
|
return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
|
|
835
840
|
}
|
|
836
841
|
|
|
842
|
+
// Return true when it is safe and useful to enter the blocking selector wait.
|
|
843
|
+
static
|
|
844
|
+
int select_blocking_allowed(struct timespec * timespec) {
|
|
845
|
+
// 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.
|
|
846
|
+
if (timeout_is_nonblocking(timespec)) {
|
|
847
|
+
return 0;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
#ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
|
|
851
|
+
// 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.
|
|
852
|
+
if (IO_Event_Selector_pending_interrupt()) {
|
|
853
|
+
return 0;
|
|
854
|
+
}
|
|
855
|
+
#endif
|
|
856
|
+
|
|
857
|
+
return 1;
|
|
858
|
+
}
|
|
859
|
+
|
|
837
860
|
struct select_arguments {
|
|
838
861
|
struct IO_Event_Selector_EPoll *selector;
|
|
839
862
|
|
|
840
863
|
int count;
|
|
864
|
+
int result;
|
|
841
865
|
struct epoll_event events[EPOLL_MAX_EVENTS];
|
|
842
866
|
|
|
843
867
|
struct timespec * timeout;
|
|
@@ -851,7 +875,7 @@ static int make_timeout_ms(struct timespec * timeout) {
|
|
|
851
875
|
return -1;
|
|
852
876
|
}
|
|
853
877
|
|
|
854
|
-
if (
|
|
878
|
+
if (timeout_is_nonblocking(timeout)) {
|
|
855
879
|
return 0;
|
|
856
880
|
}
|
|
857
881
|
|
|
@@ -872,13 +896,13 @@ void * select_internal(void *_arguments) {
|
|
|
872
896
|
struct select_arguments * arguments = (struct select_arguments *)_arguments;
|
|
873
897
|
|
|
874
898
|
#if defined(HAVE_EPOLL_PWAIT2)
|
|
875
|
-
arguments->
|
|
899
|
+
arguments->result = epoll_pwait2(arguments->selector->descriptor, arguments->events, arguments->count, arguments->timeout, NULL);
|
|
876
900
|
|
|
877
901
|
// Comment out the above line and enable the below lines to test ENOSYS code path.
|
|
878
|
-
// arguments->
|
|
902
|
+
// arguments->result = -1;
|
|
879
903
|
// errno = ENOSYS;
|
|
880
904
|
|
|
881
|
-
if (!enosys_error(arguments->
|
|
905
|
+
if (!enosys_error(arguments->result)) {
|
|
882
906
|
return NULL;
|
|
883
907
|
}
|
|
884
908
|
else {
|
|
@@ -886,37 +910,47 @@ void * select_internal(void *_arguments) {
|
|
|
886
910
|
}
|
|
887
911
|
#endif
|
|
888
912
|
|
|
889
|
-
arguments->
|
|
913
|
+
arguments->result = epoll_wait(arguments->selector->descriptor, arguments->events, arguments->count, make_timeout_ms(arguments->timeout));
|
|
890
914
|
|
|
891
915
|
return NULL;
|
|
892
916
|
}
|
|
893
917
|
|
|
894
918
|
static
|
|
895
|
-
|
|
919
|
+
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
920
|
+
arguments->result = -1;
|
|
896
921
|
arguments->selector->blocked = 1;
|
|
897
|
-
|
|
922
|
+
#ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
|
|
923
|
+
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTERRUPT_FAIL);
|
|
924
|
+
#else
|
|
925
|
+
rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
926
|
+
#endif
|
|
898
927
|
arguments->selector->blocked = 0;
|
|
899
928
|
|
|
900
|
-
if (arguments->
|
|
901
|
-
|
|
929
|
+
if (arguments->result == -1) {
|
|
930
|
+
// 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.
|
|
931
|
+
if (errno != EINTR && errno != 0) {
|
|
902
932
|
rb_sys_fail("select_internal_without_gvl:epoll_wait");
|
|
903
933
|
} else {
|
|
904
|
-
|
|
934
|
+
return 0;
|
|
905
935
|
}
|
|
906
936
|
}
|
|
937
|
+
|
|
938
|
+
return arguments->result;
|
|
907
939
|
}
|
|
908
940
|
|
|
909
941
|
static
|
|
910
|
-
|
|
942
|
+
int select_internal_with_gvl(struct select_arguments *arguments) {
|
|
911
943
|
select_internal((void *)arguments);
|
|
912
944
|
|
|
913
|
-
if (arguments->
|
|
945
|
+
if (arguments->result == -1) {
|
|
914
946
|
if (errno != EINTR) {
|
|
915
947
|
rb_sys_fail("select_internal_with_gvl:epoll_wait");
|
|
916
948
|
} else {
|
|
917
|
-
|
|
949
|
+
return 0;
|
|
918
950
|
}
|
|
919
951
|
}
|
|
952
|
+
|
|
953
|
+
return arguments->result;
|
|
920
954
|
}
|
|
921
955
|
|
|
922
956
|
static
|
|
@@ -972,7 +1006,7 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
972
1006
|
struct select_arguments *arguments = (struct select_arguments *)_arguments;
|
|
973
1007
|
struct IO_Event_Selector_EPoll *selector = arguments->selector;
|
|
974
1008
|
|
|
975
|
-
for (int i = 0; i < arguments->
|
|
1009
|
+
for (int i = 0; i < arguments->result; i += 1) {
|
|
976
1010
|
const struct epoll_event *event = &arguments->events[i];
|
|
977
1011
|
if (DEBUG) fprintf(stderr, "-> fd=%d events=%d\n", event->data.fd, event->events);
|
|
978
1012
|
|
|
@@ -983,7 +1017,7 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
983
1017
|
}
|
|
984
1018
|
}
|
|
985
1019
|
|
|
986
|
-
return INT2NUM(arguments->
|
|
1020
|
+
return INT2NUM(arguments->result);
|
|
987
1021
|
}
|
|
988
1022
|
|
|
989
1023
|
static
|
|
@@ -1008,6 +1042,8 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1008
1042
|
|
|
1009
1043
|
struct select_arguments arguments = {
|
|
1010
1044
|
.selector = selector,
|
|
1045
|
+
.count = EPOLL_MAX_EVENTS,
|
|
1046
|
+
.result = 0,
|
|
1011
1047
|
.storage = {
|
|
1012
1048
|
.tv_sec = 0,
|
|
1013
1049
|
.tv_nsec = 0
|
|
@@ -1018,22 +1054,22 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1018
1054
|
arguments.timeout = &arguments.storage;
|
|
1019
1055
|
|
|
1020
1056
|
// Process any currently pending events:
|
|
1021
|
-
select_internal_with_gvl(&arguments);
|
|
1057
|
+
int result = select_internal_with_gvl(&arguments);
|
|
1022
1058
|
|
|
1023
1059
|
// If we:
|
|
1024
1060
|
// 1. Didn't process any ready fibers, and
|
|
1025
1061
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1026
1062
|
// 3. There are no items in the ready list,
|
|
1027
1063
|
// then we can perform a blocking select.
|
|
1028
|
-
if (!ready && !
|
|
1064
|
+
if (!ready && !result && !selector->backend.ready) {
|
|
1029
1065
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1030
1066
|
|
|
1031
|
-
if (
|
|
1067
|
+
if (select_blocking_allowed(arguments.timeout)) {
|
|
1032
1068
|
struct timespec start_time;
|
|
1033
1069
|
IO_Event_Time_current(&start_time);
|
|
1034
1070
|
|
|
1035
1071
|
// Wait for events to occur:
|
|
1036
|
-
select_internal_without_gvl(&arguments);
|
|
1072
|
+
result = select_internal_without_gvl(&arguments);
|
|
1037
1073
|
|
|
1038
1074
|
struct timespec end_time;
|
|
1039
1075
|
IO_Event_Time_current(&end_time);
|
|
@@ -1041,7 +1077,7 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1041
1077
|
}
|
|
1042
1078
|
}
|
|
1043
1079
|
|
|
1044
|
-
if (
|
|
1080
|
+
if (result) {
|
|
1045
1081
|
return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments);
|
|
1046
1082
|
} else {
|
|
1047
1083
|
return RB_INT2NUM(0);
|
|
@@ -469,7 +469,7 @@ VALUE process_wait_transfer(VALUE _arguments) {
|
|
|
469
469
|
|
|
470
470
|
if (arguments->waiting->ready) {
|
|
471
471
|
process_prewait(arguments->pid);
|
|
472
|
-
return
|
|
472
|
+
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
473
473
|
} else {
|
|
474
474
|
return Qfalse;
|
|
475
475
|
}
|
|
@@ -493,6 +493,11 @@ VALUE IO_Event_Selector_KQueue_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
493
493
|
pid_t pid = NUM2PIDT(_pid);
|
|
494
494
|
int flags = NUM2INT(_flags);
|
|
495
495
|
|
|
496
|
+
// `EVFILT_PROC` can only refer to a specific process, so waiting for any child or a process group (pid <= 0) is delegated to the threaded fallback:
|
|
497
|
+
if (pid <= 0) {
|
|
498
|
+
return IO_Event_Selector_process_wait(pid, flags);
|
|
499
|
+
}
|
|
500
|
+
|
|
496
501
|
struct IO_Event_Selector_KQueue_Waiting waiting = {
|
|
497
502
|
.list = {.type = &IO_Event_Selector_KQueue_process_wait_list_type},
|
|
498
503
|
.fiber = fiber,
|
|
@@ -514,7 +519,7 @@ VALUE IO_Event_Selector_KQueue_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
514
519
|
if (errno == ESRCH) {
|
|
515
520
|
process_prewait(pid);
|
|
516
521
|
|
|
517
|
-
return
|
|
522
|
+
return IO_Event_Selector_process_status_reap(pid, flags);
|
|
518
523
|
}
|
|
519
524
|
|
|
520
525
|
rb_sys_fail("IO_Event_Selector_KQueue_process_wait:IO_Event_Selector_KQueue_Waiting_register");
|
|
@@ -838,15 +843,29 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
|
|
|
838
843
|
return storage;
|
|
839
844
|
}
|
|
840
845
|
|
|
846
|
+
// Return true when it is safe and useful to enter the blocking selector wait.
|
|
841
847
|
static
|
|
842
|
-
int
|
|
843
|
-
|
|
848
|
+
int select_blocking_allowed(struct timespec * timespec) {
|
|
849
|
+
// 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.
|
|
850
|
+
if (timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0) {
|
|
851
|
+
return 0;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
#ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
|
|
855
|
+
// 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.
|
|
856
|
+
if (IO_Event_Selector_pending_interrupt()) {
|
|
857
|
+
return 0;
|
|
858
|
+
}
|
|
859
|
+
#endif
|
|
860
|
+
|
|
861
|
+
return 1;
|
|
844
862
|
}
|
|
845
863
|
|
|
846
864
|
struct select_arguments {
|
|
847
865
|
struct IO_Event_Selector_KQueue *selector;
|
|
848
866
|
|
|
849
867
|
int count;
|
|
868
|
+
int result;
|
|
850
869
|
struct kevent events[KQUEUE_MAX_EVENTS];
|
|
851
870
|
|
|
852
871
|
struct timespec storage;
|
|
@@ -859,38 +878,48 @@ static
|
|
|
859
878
|
void * select_internal(void *_arguments) {
|
|
860
879
|
struct select_arguments * arguments = (struct select_arguments *)_arguments;
|
|
861
880
|
|
|
862
|
-
arguments->
|
|
881
|
+
arguments->result = kevent(arguments->selector->descriptor, NULL, 0, arguments->events, arguments->count, arguments->timeout);
|
|
863
882
|
|
|
864
883
|
return NULL;
|
|
865
884
|
}
|
|
866
885
|
|
|
867
886
|
static
|
|
868
|
-
|
|
887
|
+
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
888
|
+
arguments->result = -1;
|
|
869
889
|
arguments->selector->blocked = 1;
|
|
870
890
|
|
|
871
|
-
|
|
891
|
+
#ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
|
|
892
|
+
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTERRUPT_FAIL);
|
|
893
|
+
#else
|
|
894
|
+
rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
895
|
+
#endif
|
|
872
896
|
arguments->selector->blocked = 0;
|
|
873
897
|
|
|
874
|
-
if (arguments->
|
|
875
|
-
|
|
898
|
+
if (arguments->result == -1) {
|
|
899
|
+
// 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.
|
|
900
|
+
if (errno != EINTR && errno != 0) {
|
|
876
901
|
rb_sys_fail("select_internal_without_gvl:kevent");
|
|
877
902
|
} else {
|
|
878
|
-
|
|
903
|
+
return 0;
|
|
879
904
|
}
|
|
880
905
|
}
|
|
906
|
+
|
|
907
|
+
return arguments->result;
|
|
881
908
|
}
|
|
882
909
|
|
|
883
910
|
static
|
|
884
|
-
|
|
911
|
+
int select_internal_with_gvl(struct select_arguments *arguments) {
|
|
885
912
|
select_internal((void *)arguments);
|
|
886
913
|
|
|
887
|
-
if (arguments->
|
|
914
|
+
if (arguments->result == -1) {
|
|
888
915
|
if (errno != EINTR) {
|
|
889
916
|
rb_sys_fail("select_internal_with_gvl:kevent");
|
|
890
917
|
} else {
|
|
891
|
-
|
|
918
|
+
return 0;
|
|
892
919
|
}
|
|
893
920
|
}
|
|
921
|
+
|
|
922
|
+
return arguments->result;
|
|
894
923
|
}
|
|
895
924
|
|
|
896
925
|
static
|
|
@@ -944,14 +973,14 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
944
973
|
struct select_arguments *arguments = (struct select_arguments *)_arguments;
|
|
945
974
|
struct IO_Event_Selector_KQueue *selector = arguments->selector;
|
|
946
975
|
|
|
947
|
-
for (int i = 0; i < arguments->
|
|
976
|
+
for (int i = 0; i < arguments->result; i += 1) {
|
|
948
977
|
if (arguments->events[i].udata) {
|
|
949
978
|
struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata;
|
|
950
979
|
kqueue_descriptor->ready_events |= events_from_kevent_filter(arguments->events[i].filter);
|
|
951
980
|
}
|
|
952
981
|
}
|
|
953
982
|
|
|
954
|
-
for (int i = 0; i < arguments->
|
|
983
|
+
for (int i = 0; i < arguments->result; i += 1) {
|
|
955
984
|
if (arguments->events[i].udata) {
|
|
956
985
|
struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata;
|
|
957
986
|
IO_Event_Selector_KQueue_handle(selector, arguments->events[i].ident, kqueue_descriptor, &arguments->saved);
|
|
@@ -962,7 +991,7 @@ VALUE select_handle_events(VALUE _arguments)
|
|
|
962
991
|
}
|
|
963
992
|
}
|
|
964
993
|
|
|
965
|
-
return RB_INT2NUM(arguments->
|
|
994
|
+
return RB_INT2NUM(arguments->result);
|
|
966
995
|
}
|
|
967
996
|
|
|
968
997
|
static
|
|
@@ -987,6 +1016,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
987
1016
|
struct select_arguments arguments = {
|
|
988
1017
|
.selector = selector,
|
|
989
1018
|
.count = KQUEUE_MAX_EVENTS,
|
|
1019
|
+
.result = 0,
|
|
990
1020
|
.storage = {
|
|
991
1021
|
.tv_sec = 0,
|
|
992
1022
|
.tv_nsec = 0
|
|
@@ -997,14 +1027,14 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
997
1027
|
arguments.timeout = &arguments.storage;
|
|
998
1028
|
|
|
999
1029
|
// We break this implementation into two parts.
|
|
1000
|
-
// (1)
|
|
1001
|
-
// (2) without gvl: kevent(..., timeout = 0) if
|
|
1030
|
+
// (1) result = kevent(..., timeout = 0)
|
|
1031
|
+
// (2) without gvl: kevent(..., timeout = 0) if result == 0 and timeout != 0
|
|
1002
1032
|
// This allows us to avoid releasing and reacquiring the GVL.
|
|
1003
1033
|
// Non-comprehensive testing shows this gives a 1.5x speedup.
|
|
1004
1034
|
|
|
1005
1035
|
// First do the syscall with no timeout to get any immediately available events:
|
|
1006
1036
|
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);
|
|
1037
|
+
int result = select_internal_with_gvl(&arguments);
|
|
1008
1038
|
if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl done\r\n");
|
|
1009
1039
|
|
|
1010
1040
|
// If we:
|
|
@@ -1012,17 +1042,15 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
1012
1042
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1013
1043
|
// 3. There are no items in the ready list,
|
|
1014
1044
|
// then we can perform a blocking select.
|
|
1015
|
-
if (!ready && !
|
|
1045
|
+
if (!ready && !result && !selector->backend.ready) {
|
|
1016
1046
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1017
1047
|
|
|
1018
|
-
if (
|
|
1019
|
-
arguments.count = KQUEUE_MAX_EVENTS;
|
|
1020
|
-
|
|
1048
|
+
if (select_blocking_allowed(arguments.timeout)) {
|
|
1021
1049
|
struct timespec start_time;
|
|
1022
1050
|
IO_Event_Time_current(&start_time);
|
|
1023
1051
|
|
|
1024
1052
|
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);
|
|
1053
|
+
result = select_internal_without_gvl(&arguments);
|
|
1026
1054
|
|
|
1027
1055
|
struct timespec end_time;
|
|
1028
1056
|
IO_Event_Time_current(&end_time);
|
|
@@ -1030,7 +1058,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
1030
1058
|
}
|
|
1031
1059
|
}
|
|
1032
1060
|
|
|
1033
|
-
if (
|
|
1061
|
+
if (result) {
|
|
1034
1062
|
return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments);
|
|
1035
1063
|
} else {
|
|
1036
1064
|
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
|
|
|
@@ -22,7 +24,7 @@ static VALUE rb_Process_Status = Qnil;
|
|
|
22
24
|
|
|
23
25
|
VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags)
|
|
24
26
|
{
|
|
25
|
-
return rb_funcall(rb_Process_Status, id_wait, 2, PIDT2NUM(pid), INT2NUM(flags
|
|
27
|
+
return rb_funcall(rb_Process_Status, id_wait, 2, PIDT2NUM(pid), INT2NUM(flags));
|
|
26
28
|
}
|
|
27
29
|
#endif
|
|
28
30
|
|
|
@@ -78,7 +80,21 @@ static VALUE IO_Event_Selector_nonblock(VALUE class, VALUE io)
|
|
|
78
80
|
return rb_ensure(rb_yield, io, IO_Event_Selector_nonblock_ensure, (VALUE)&arguments);
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
static VALUE rb_IO_Event_Selector = Qnil;
|
|
84
|
+
static ID id_process_wait;
|
|
85
|
+
|
|
86
|
+
// Wait for a process when the selector cannot do so natively (e.g. `pid <= 0`: any child, or a process group). Delegates to the pure-Ruby `IO::Event::Selector.process_wait`, which performs a blocking wait on a separate thread; joining it is fiber-scheduler aware, so the reactor keeps running.
|
|
87
|
+
VALUE IO_Event_Selector_process_wait(rb_pid_t pid, int flags) {
|
|
88
|
+
return rb_funcall(rb_IO_Event_Selector, id_process_wait, 2, PIDT2NUM(pid), INT2NUM(flags));
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
|
|
92
|
+
IO_Event_Selector_pending_interrupt_p_id = rb_intern("pending_interrupt?");
|
|
93
|
+
|
|
94
|
+
rb_IO_Event_Selector = IO_Event_Selector;
|
|
95
|
+
rb_gc_register_mark_object(rb_IO_Event_Selector);
|
|
96
|
+
id_process_wait = rb_intern("process_wait");
|
|
97
|
+
|
|
82
98
|
#ifndef HAVE_RB_IO_DESCRIPTOR
|
|
83
99
|
id_fileno = rb_intern("fileno");
|
|
84
100
|
#endif
|
|
@@ -40,19 +40,33 @@ 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
|
|
46
52
|
int IO_Event_Selector_io_descriptor(VALUE io);
|
|
47
53
|
#endif
|
|
48
54
|
|
|
49
|
-
//
|
|
55
|
+
// Wait for a process to change state. This blocks until the process changes state, unless `WNOHANG` is given in `flags`.
|
|
50
56
|
#ifdef HAVE_RB_PROCESS_STATUS_WAIT
|
|
51
|
-
#define IO_Event_Selector_process_status_wait(pid, flags) rb_process_status_wait(pid, flags
|
|
57
|
+
#define IO_Event_Selector_process_status_wait(pid, flags) rb_process_status_wait(pid, flags)
|
|
52
58
|
#else
|
|
53
59
|
VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags);
|
|
54
60
|
#endif
|
|
55
61
|
|
|
62
|
+
// Reap a process that is known to have changed state (e.g. after a readiness event), without blocking.
|
|
63
|
+
static inline VALUE IO_Event_Selector_process_status_reap(rb_pid_t pid, int flags) {
|
|
64
|
+
return IO_Event_Selector_process_status_wait(pid, flags | WNOHANG);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Wait for a process the selector cannot represent natively (e.g. `pid <= 0`: any child, or a process group), using a fiber-scheduler aware blocking wait on a separate thread.
|
|
68
|
+
VALUE IO_Event_Selector_process_wait(rb_pid_t pid, int flags);
|
|
69
|
+
|
|
56
70
|
int IO_Event_Selector_nonblock_set(int file_descriptor);
|
|
57
71
|
void IO_Event_Selector_nonblock_restore(int file_descriptor, int flags);
|
|
58
72
|
|
|
@@ -15,10 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
#include "../interrupt.h"
|
|
17
17
|
|
|
18
|
-
#include "pidfd.c"
|
|
19
|
-
|
|
20
18
|
#include <linux/version.h>
|
|
21
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
|
+
|
|
22
29
|
enum {
|
|
23
30
|
DEBUG = 0,
|
|
24
31
|
DEBUG_COMPLETION = 0,
|
|
@@ -500,13 +507,43 @@ struct io_uring_sqe * io_get_sqe(struct IO_Event_Selector_URing *selector) {
|
|
|
500
507
|
|
|
501
508
|
#pragma mark - Process.wait
|
|
502
509
|
|
|
510
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
511
|
+
// Translate a Ruby/`waitpid`-style pid into the `waitid(2)` idtype and id, mirroring the semantics of `waitpid(2)`:
|
|
512
|
+
//
|
|
513
|
+
// pid == -1 -> any child (P_ALL)
|
|
514
|
+
// pid == 0 -> any child in the caller's process group (P_PGID, id 0; Linux >= 5.4)
|
|
515
|
+
// pid < -1 -> any child in process group |pid| (P_PGID)
|
|
516
|
+
// pid > 0 -> the specific child (P_PID)
|
|
517
|
+
//
|
|
518
|
+
static inline idtype_t process_waitid_type(pid_t pid, id_t *id) {
|
|
519
|
+
if (pid == -1) {
|
|
520
|
+
*id = 0;
|
|
521
|
+
return P_ALL;
|
|
522
|
+
} else if (pid == 0) {
|
|
523
|
+
*id = 0;
|
|
524
|
+
return P_PGID;
|
|
525
|
+
} else if (pid < -1) {
|
|
526
|
+
*id = (id_t)(-pid);
|
|
527
|
+
return P_PGID;
|
|
528
|
+
} else {
|
|
529
|
+
*id = (id_t)pid;
|
|
530
|
+
return P_PID;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
#endif
|
|
534
|
+
|
|
503
535
|
struct process_wait_arguments {
|
|
504
536
|
struct IO_Event_Selector_URing *selector;
|
|
505
537
|
struct IO_Event_Selector_URing_Waiting *waiting;
|
|
506
538
|
|
|
507
539
|
pid_t pid;
|
|
508
540
|
int flags;
|
|
541
|
+
|
|
542
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
543
|
+
siginfo_t siginfo;
|
|
544
|
+
#else
|
|
509
545
|
int descriptor;
|
|
546
|
+
#endif
|
|
510
547
|
};
|
|
511
548
|
|
|
512
549
|
static
|
|
@@ -515,18 +552,32 @@ VALUE process_wait_transfer(VALUE _arguments) {
|
|
|
515
552
|
|
|
516
553
|
IO_Event_Selector_loop_yield(&arguments->selector->backend);
|
|
517
554
|
|
|
555
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
556
|
+
int32_t result = arguments->waiting->result;
|
|
557
|
+
if (result < 0) {
|
|
558
|
+
rb_syserr_fail(-result, "IO_Event_Selector_URing_process_wait:io_uring_prep_waitid");
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
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);
|
|
562
|
+
|
|
563
|
+
// 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`:
|
|
564
|
+
return IO_Event_Selector_process_status_reap(arguments->siginfo.si_pid, arguments->flags);
|
|
565
|
+
#else
|
|
518
566
|
if (arguments->waiting->result) {
|
|
519
|
-
return
|
|
567
|
+
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
520
568
|
} else {
|
|
521
569
|
return Qfalse;
|
|
522
570
|
}
|
|
571
|
+
#endif
|
|
523
572
|
}
|
|
524
573
|
|
|
525
574
|
static
|
|
526
575
|
VALUE process_wait_ensure(VALUE _arguments) {
|
|
527
576
|
struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
|
|
528
577
|
|
|
578
|
+
#ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
529
579
|
close(arguments->descriptor);
|
|
580
|
+
#endif
|
|
530
581
|
|
|
531
582
|
IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
|
|
532
583
|
|
|
@@ -540,11 +591,18 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
540
591
|
pid_t pid = NUM2PIDT(_pid);
|
|
541
592
|
int flags = NUM2INT(_flags);
|
|
542
593
|
|
|
594
|
+
#ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
595
|
+
// `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:
|
|
596
|
+
if (pid <= 0) {
|
|
597
|
+
return IO_Event_Selector_process_wait(pid, flags);
|
|
598
|
+
}
|
|
599
|
+
|
|
543
600
|
int descriptor = pidfd_open(pid, 0);
|
|
544
601
|
if (descriptor < 0) {
|
|
545
602
|
rb_syserr_fail(errno, "IO_Event_Selector_URing_process_wait:pidfd_open");
|
|
546
603
|
}
|
|
547
604
|
rb_update_max_fd(descriptor);
|
|
605
|
+
#endif
|
|
548
606
|
|
|
549
607
|
struct IO_Event_Selector_URing_Waiting waiting = {
|
|
550
608
|
.fiber = fiber,
|
|
@@ -559,12 +617,25 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
559
617
|
.waiting = &waiting,
|
|
560
618
|
.pid = pid,
|
|
561
619
|
.flags = flags,
|
|
620
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
621
|
+
.siginfo = {0},
|
|
622
|
+
#else
|
|
562
623
|
.descriptor = descriptor,
|
|
624
|
+
#endif
|
|
563
625
|
};
|
|
564
626
|
|
|
565
|
-
if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
|
|
566
627
|
struct io_uring_sqe *sqe = io_get_sqe(selector);
|
|
628
|
+
|
|
629
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
630
|
+
id_t id;
|
|
631
|
+
idtype_t idtype = process_waitid_type(pid, &id);
|
|
632
|
+
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);
|
|
633
|
+
// `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`:
|
|
634
|
+
io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED | WNOWAIT, 0);
|
|
635
|
+
#else
|
|
636
|
+
if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
|
|
567
637
|
io_uring_prep_poll_add(sqe, descriptor, POLLIN|POLLHUP|POLLERR);
|
|
638
|
+
#endif
|
|
568
639
|
io_uring_sqe_set_data(sqe, completion);
|
|
569
640
|
io_uring_submit_pending(selector);
|
|
570
641
|
|
|
@@ -1130,9 +1201,22 @@ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec
|
|
|
1130
1201
|
return storage;
|
|
1131
1202
|
}
|
|
1132
1203
|
|
|
1204
|
+
// Return true when it is safe and useful to enter the blocking selector wait.
|
|
1133
1205
|
static
|
|
1134
|
-
int
|
|
1135
|
-
|
|
1206
|
+
int select_blocking_allowed(struct __kernel_timespec *timespec) {
|
|
1207
|
+
// 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.
|
|
1208
|
+
if (timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0) {
|
|
1209
|
+
return 0;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
#ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
|
|
1213
|
+
// 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.
|
|
1214
|
+
if (IO_Event_Selector_pending_interrupt()) {
|
|
1215
|
+
return 0;
|
|
1216
|
+
}
|
|
1217
|
+
#endif
|
|
1218
|
+
|
|
1219
|
+
return 1;
|
|
1136
1220
|
}
|
|
1137
1221
|
|
|
1138
1222
|
struct select_arguments {
|
|
@@ -1171,22 +1255,27 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
|
1171
1255
|
|
|
1172
1256
|
io_uring_submit_flush(selector);
|
|
1173
1257
|
|
|
1258
|
+
arguments->result = -EINTR;
|
|
1174
1259
|
selector->blocked = 1;
|
|
1175
|
-
|
|
1260
|
+
#ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
|
|
1261
|
+
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTERRUPT_FAIL);
|
|
1262
|
+
#else
|
|
1263
|
+
rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
1264
|
+
#endif
|
|
1176
1265
|
selector->blocked = 0;
|
|
1177
1266
|
|
|
1178
1267
|
if (arguments->result == -ETIME) {
|
|
1179
|
-
|
|
1268
|
+
return 0;
|
|
1180
1269
|
} else if (arguments->result == -EINTR) {
|
|
1181
|
-
|
|
1270
|
+
return 0;
|
|
1182
1271
|
} else if (arguments->result < 0) {
|
|
1183
1272
|
rb_syserr_fail(-arguments->result, "select_internal_without_gvl:io_uring_wait_cqe_timeout");
|
|
1184
1273
|
} else {
|
|
1185
1274
|
// At least 1 event is waiting:
|
|
1186
|
-
|
|
1275
|
+
return 1;
|
|
1187
1276
|
}
|
|
1188
1277
|
|
|
1189
|
-
return
|
|
1278
|
+
return 0;
|
|
1190
1279
|
}
|
|
1191
1280
|
|
|
1192
1281
|
static inline
|
|
@@ -1283,28 +1372,29 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
|
|
|
1283
1372
|
|
|
1284
1373
|
int ready = IO_Event_Selector_ready_flush(&selector->backend);
|
|
1285
1374
|
|
|
1286
|
-
int
|
|
1375
|
+
int completed = select_process_completions(selector);
|
|
1287
1376
|
|
|
1288
1377
|
// If we:
|
|
1289
1378
|
// 1. Didn't process any ready fibers, and
|
|
1290
1379
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1291
1380
|
// 3. There are no items in the ready list,
|
|
1292
1381
|
// then we can perform a blocking select.
|
|
1293
|
-
if (!ready && !
|
|
1382
|
+
if (!ready && !completed && !selector->backend.ready) {
|
|
1294
1383
|
// We might need to wait for events:
|
|
1295
1384
|
struct select_arguments arguments = {
|
|
1296
1385
|
.selector = selector,
|
|
1386
|
+
.result = 0,
|
|
1297
1387
|
.timeout = NULL,
|
|
1298
1388
|
};
|
|
1299
1389
|
|
|
1300
1390
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1301
1391
|
|
|
1302
|
-
if (!selector->backend.ready &&
|
|
1392
|
+
if (!selector->backend.ready && select_blocking_allowed(arguments.timeout)) {
|
|
1303
1393
|
struct timespec start_time;
|
|
1304
1394
|
IO_Event_Time_current(&start_time);
|
|
1305
1395
|
|
|
1306
1396
|
// This is a blocking operation, we wait for events:
|
|
1307
|
-
result = select_internal_without_gvl(&arguments);
|
|
1397
|
+
int result = select_internal_without_gvl(&arguments);
|
|
1308
1398
|
|
|
1309
1399
|
struct timespec end_time;
|
|
1310
1400
|
IO_Event_Time_current(&end_time);
|
|
@@ -1312,12 +1402,12 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
|
|
|
1312
1402
|
|
|
1313
1403
|
// After waiting/flushing the SQ, check if there are any completions:
|
|
1314
1404
|
if (result > 0) {
|
|
1315
|
-
|
|
1405
|
+
completed = select_process_completions(selector);
|
|
1316
1406
|
}
|
|
1317
1407
|
}
|
|
1318
1408
|
}
|
|
1319
1409
|
|
|
1320
|
-
return RB_INT2NUM(
|
|
1410
|
+
return RB_INT2NUM(completed);
|
|
1321
1411
|
}
|
|
1322
1412
|
|
|
1323
1413
|
VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
|
data/lib/io/event/interrupt.rb
CHANGED
|
@@ -278,9 +278,7 @@ module IO::Event
|
|
|
278
278
|
# @parameter flags [Integer] Flags to pass to Process::Status.wait.
|
|
279
279
|
# @returns [Process::Status] The status of the waited process.
|
|
280
280
|
def process_wait(fiber, pid, flags)
|
|
281
|
-
|
|
282
|
-
Process::Status.wait(pid, flags)
|
|
283
|
-
end.value
|
|
281
|
+
Selector.process_wait(pid, flags)
|
|
284
282
|
end
|
|
285
283
|
|
|
286
284
|
private def pop_ready
|
data/lib/io/event/selector.rb
CHANGED
|
@@ -40,5 +40,23 @@ module IO::Event
|
|
|
40
40
|
|
|
41
41
|
return selector
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
# Wait for a process to change state, for the cases a selector cannot represent natively (e.g. `pid <= 0`: any child, or a process group). The native selectors integrate process waiting with the event loop using per-process primitives (`pidfd_open`, `EVFILT_PROC`) which can only refer to a single, specific process, and delegate here otherwise.
|
|
45
|
+
#
|
|
46
|
+
# The wait is performed on a separate thread, which has no fiber scheduler and therefore blocks. Joining it via `Thread#value` is fiber-scheduler aware, so the calling fiber yields to the event loop and the reactor keeps running other fibers.
|
|
47
|
+
#
|
|
48
|
+
# @parameter pid [Integer] The process ID (or process group) to wait for.
|
|
49
|
+
# @parameter flags [Integer] Flags to pass to `Process::Status.wait`.
|
|
50
|
+
# @returns [Process::Status] The status of the waited process.
|
|
51
|
+
def self.process_wait(pid, flags)
|
|
52
|
+
thread = ::Thread.new do
|
|
53
|
+
::Process::Status.wait(pid, flags)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
thread.value
|
|
57
|
+
ensure
|
|
58
|
+
# If the calling fiber was interrupted before the wait completed, don't leave the thread running:
|
|
59
|
+
thread&.kill
|
|
60
|
+
end
|
|
43
61
|
end
|
|
44
62
|
end
|
data/lib/io/event/version.rb
CHANGED
data/license.md
CHANGED
|
@@ -19,6 +19,7 @@ Copyright, 2026, by John Hawthorn.
|
|
|
19
19
|
Copyright, 2026, by Italo Brandão.
|
|
20
20
|
Copyright, 2026, by Fletcher Dares.
|
|
21
21
|
Copyright, 2026, by Tavian Barnes.
|
|
22
|
+
Copyright, 2026, by Sharon Rosner.
|
|
22
23
|
|
|
23
24
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
24
25
|
of this software and associated documentation files (the "Software"), to deal
|
data/readme.md
CHANGED
|
@@ -18,6 +18,15 @@ 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.19.0
|
|
22
|
+
|
|
23
|
+
- Use `io_uring_prep_waitid` for `process_wait` in the `URing` selector (Linux 6.7+), waiting for child exit directly in the ring instead of polling on a `pidfd`. The child is reaped via `rb_process_status_wait` (using `WEXITED | WNOWAIT`) to construct a correct `Process::Status`, and `process_wait(-1, ...)` / `process_wait(0, ...)` are now supported.
|
|
24
|
+
- Support waiting for any child or a process group (`pid <= 0`) on all selectors. The `EPoll` (`pidfd_open`) and `KQueue` (`EVFILT_PROC`) selectors can only watch a specific process, so these cases now fall back to a blocking wait on a dedicated thread; joining it is fiber-scheduler aware, so the reactor keeps running.
|
|
25
|
+
|
|
26
|
+
### v1.18.0
|
|
27
|
+
|
|
28
|
+
- **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
|
|
29
|
+
|
|
21
30
|
### v1.17.0
|
|
22
31
|
|
|
23
32
|
- Report inherited selector objects as closed after fork, and avoid closing descriptors they no longer own.
|
|
@@ -56,14 +65,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
56
65
|
|
|
57
66
|
- Add bounds checks, in the unlikely event of a user providing an invalid offset that exceeds the buffer size. This prevents potential memory corruption and ensures safe operation when using buffered IO methods.
|
|
58
67
|
|
|
59
|
-
### v1.14.4
|
|
60
|
-
|
|
61
|
-
- Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.
|
|
62
|
-
|
|
63
|
-
### v1.14.3
|
|
64
|
-
|
|
65
|
-
- Fix several implementation bugs that could cause deadlocks on blocking writes.
|
|
66
|
-
|
|
67
68
|
## Contributing
|
|
68
69
|
|
|
69
70
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v1.19.0
|
|
4
|
+
|
|
5
|
+
- Use `io_uring_prep_waitid` for `process_wait` in the `URing` selector (Linux 6.7+), waiting for child exit directly in the ring instead of polling on a `pidfd`. The child is reaped via `rb_process_status_wait` (using `WEXITED | WNOWAIT`) to construct a correct `Process::Status`, and `process_wait(-1, ...)` / `process_wait(0, ...)` are now supported.
|
|
6
|
+
- Support waiting for any child or a process group (`pid <= 0`) on all selectors. The `EPoll` (`pidfd_open`) and `KQueue` (`EVFILT_PROC`) selectors can only watch a specific process, so these cases now fall back to a blocking wait on a dedicated thread; joining it is fiber-scheduler aware, so the reactor keeps running.
|
|
7
|
+
|
|
8
|
+
## v1.18.0
|
|
9
|
+
|
|
10
|
+
- **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
|
|
11
|
+
|
|
3
12
|
## v1.17.0
|
|
4
13
|
|
|
5
14
|
- 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
|
@@ -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.
|
|
4
|
+
version: 1.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -20,6 +20,7 @@ authors:
|
|
|
20
20
|
- John Hawthorn
|
|
21
21
|
- Luke Gruber
|
|
22
22
|
- Pavel Rosický
|
|
23
|
+
- Sharon Rosner
|
|
23
24
|
- Stan Hu
|
|
24
25
|
- Stanislav (Stas) Katkov
|
|
25
26
|
- William T. Nelson
|
metadata.gz.sig
CHANGED
|
Binary file
|