io-event 1.19.0 → 1.19.2
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 +1 -0
- data/ext/io/event/selector/epoll.c +4 -4
- data/ext/io/event/selector/kqueue.c +4 -4
- data/ext/io/event/selector/uring.c +46 -7
- data/lib/io/event/version.rb +1 -1
- data/readme.md +8 -8
- data/releases.md +8 -0
- data.tar.gz.sig +0 -0
- metadata +1 -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: ce297a0d80038a28c3814c5a1bb227453ad27b30cdf253cd20c9abfaed90dd59
|
|
4
|
+
data.tar.gz: 7017435d4b51d770524b289dedad72f39a4f166290af2767ea7a32123396f471
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d214fc9c5cd13f627e1f1be6676e1b86daa013ee27d360323df85b4f7f33d267f2f61bd2db7f8dcbbe46f8b3e448d0aca995a1b3f23cd8275048066f3c03ef05
|
|
7
|
+
data.tar.gz: 6b7f4d739d1b5bb13648575fcb0d57b2dc5965b29ae136f91cd03a200cfceb08a191476f528a487807f37b178b8e1a25021dba9b00929cc68ca7166cf081c034
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/ext/extconf.rb
CHANGED
|
@@ -847,8 +847,8 @@ int select_blocking_allowed(struct timespec * timespec) {
|
|
|
847
847
|
return 0;
|
|
848
848
|
}
|
|
849
849
|
|
|
850
|
-
#ifndef
|
|
851
|
-
// On Rubies without `
|
|
850
|
+
#ifndef RB_NOGVL_PENDING_INTR_FAIL
|
|
851
|
+
// On Rubies without `RB_NOGVL_PENDING_INTR_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
852
|
if (IO_Event_Selector_pending_interrupt()) {
|
|
853
853
|
return 0;
|
|
854
854
|
}
|
|
@@ -919,8 +919,8 @@ static
|
|
|
919
919
|
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
920
920
|
arguments->result = -1;
|
|
921
921
|
arguments->selector->blocked = 1;
|
|
922
|
-
#ifdef
|
|
923
|
-
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL |
|
|
922
|
+
#ifdef RB_NOGVL_PENDING_INTR_FAIL
|
|
923
|
+
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTR_FAIL);
|
|
924
924
|
#else
|
|
925
925
|
rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
926
926
|
#endif
|
|
@@ -851,8 +851,8 @@ int select_blocking_allowed(struct timespec * timespec) {
|
|
|
851
851
|
return 0;
|
|
852
852
|
}
|
|
853
853
|
|
|
854
|
-
#ifndef
|
|
855
|
-
// On Rubies without `
|
|
854
|
+
#ifndef RB_NOGVL_PENDING_INTR_FAIL
|
|
855
|
+
// On Rubies without `RB_NOGVL_PENDING_INTR_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
856
|
if (IO_Event_Selector_pending_interrupt()) {
|
|
857
857
|
return 0;
|
|
858
858
|
}
|
|
@@ -888,8 +888,8 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
|
888
888
|
arguments->result = -1;
|
|
889
889
|
arguments->selector->blocked = 1;
|
|
890
890
|
|
|
891
|
-
#ifdef
|
|
892
|
-
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL |
|
|
891
|
+
#ifdef RB_NOGVL_PENDING_INTR_FAIL
|
|
892
|
+
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTR_FAIL);
|
|
893
893
|
#else
|
|
894
894
|
rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
895
895
|
#endif
|
|
@@ -546,6 +546,29 @@ struct process_wait_arguments {
|
|
|
546
546
|
#endif
|
|
547
547
|
};
|
|
548
548
|
|
|
549
|
+
#if defined(IO_EVENT_SELECTOR_URING_USE_WAITID) && defined(HAVE_RB_PROCESS_STATUS_FOR)
|
|
550
|
+
static inline int process_wait_status_exited(int exit_status) {
|
|
551
|
+
return (exit_status & 0xff) << 8;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static inline int process_wait_status_signaled(int term_signal) {
|
|
555
|
+
return term_signal & 0x7f;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
static inline int process_wait_status_from_siginfo(const siginfo_t *siginfo) {
|
|
559
|
+
switch (siginfo->si_code) {
|
|
560
|
+
case CLD_EXITED:
|
|
561
|
+
return process_wait_status_exited(siginfo->si_status);
|
|
562
|
+
case CLD_KILLED:
|
|
563
|
+
return process_wait_status_signaled(siginfo->si_status);
|
|
564
|
+
case CLD_DUMPED:
|
|
565
|
+
return process_wait_status_signaled(siginfo->si_status) | 0x80;
|
|
566
|
+
default:
|
|
567
|
+
return 0;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
#endif
|
|
571
|
+
|
|
549
572
|
static
|
|
550
573
|
VALUE process_wait_transfer(VALUE _arguments) {
|
|
551
574
|
struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
|
|
@@ -554,14 +577,25 @@ VALUE process_wait_transfer(VALUE _arguments) {
|
|
|
554
577
|
|
|
555
578
|
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
556
579
|
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
580
|
|
|
561
581
|
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
582
|
|
|
583
|
+
if (result < 0) {
|
|
584
|
+
// The `waitid` failed (e.g. `ECHILD` when there are no children). Reproduce the failure as a `Process::Status` carrying the error, rather than raising, so callers like `Process.waitall` / `Process.detach` (which expect `waitpid` to report the error, not raise) behave correctly:
|
|
585
|
+
#ifdef HAVE_RB_PROCESS_STATUS_FOR
|
|
586
|
+
return rb_process_status_for(-1, 0, -result);
|
|
587
|
+
#else
|
|
588
|
+
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
589
|
+
#endif
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
#ifdef HAVE_RB_PROCESS_STATUS_FOR
|
|
593
|
+
// The `waitid` operation already reaped the child. Convert the `siginfo_t` result directly into the Ruby process status value:
|
|
594
|
+
return rb_process_status_for(arguments->siginfo.si_pid, process_wait_status_from_siginfo(&arguments->siginfo), 0);
|
|
595
|
+
#else
|
|
563
596
|
// 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
597
|
return IO_Event_Selector_process_status_reap(arguments->siginfo.si_pid, arguments->flags);
|
|
598
|
+
#endif
|
|
565
599
|
#else
|
|
566
600
|
if (arguments->waiting->result) {
|
|
567
601
|
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
@@ -630,8 +664,13 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
630
664
|
id_t id;
|
|
631
665
|
idtype_t idtype = process_waitid_type(pid, &id);
|
|
632
666
|
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);
|
|
667
|
+
#ifdef HAVE_RB_PROCESS_STATUS_FOR
|
|
668
|
+
// Reap the child directly; the completion contains enough information to construct the Ruby process status value:
|
|
669
|
+
io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED, 0);
|
|
670
|
+
#else
|
|
633
671
|
// `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
672
|
io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED | WNOWAIT, 0);
|
|
673
|
+
#endif
|
|
635
674
|
#else
|
|
636
675
|
if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
|
|
637
676
|
io_uring_prep_poll_add(sqe, descriptor, POLLIN|POLLHUP|POLLERR);
|
|
@@ -1209,8 +1248,8 @@ int select_blocking_allowed(struct __kernel_timespec *timespec) {
|
|
|
1209
1248
|
return 0;
|
|
1210
1249
|
}
|
|
1211
1250
|
|
|
1212
|
-
#ifndef
|
|
1213
|
-
// On Rubies without `
|
|
1251
|
+
#ifndef RB_NOGVL_PENDING_INTR_FAIL
|
|
1252
|
+
// On Rubies without `RB_NOGVL_PENDING_INTR_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
1253
|
if (IO_Event_Selector_pending_interrupt()) {
|
|
1215
1254
|
return 0;
|
|
1216
1255
|
}
|
|
@@ -1257,8 +1296,8 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
|
1257
1296
|
|
|
1258
1297
|
arguments->result = -EINTR;
|
|
1259
1298
|
selector->blocked = 1;
|
|
1260
|
-
#ifdef
|
|
1261
|
-
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL |
|
|
1299
|
+
#ifdef RB_NOGVL_PENDING_INTR_FAIL
|
|
1300
|
+
rb_nogvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTR_FAIL);
|
|
1262
1301
|
#else
|
|
1263
1302
|
rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
1264
1303
|
#endif
|
data/lib/io/event/version.rb
CHANGED
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.19.2
|
|
22
|
+
|
|
23
|
+
- Use `rb_process_status_for` when available to construct `URing` `process_wait` results directly from `waitid`, avoiding the extra reap syscall previously needed to build a `Process::Status`.
|
|
24
|
+
|
|
25
|
+
### v1.19.1
|
|
26
|
+
|
|
27
|
+
- Fix `Process.waitall` / `Process.detach` under the `URing` selector: when `io_uring`'s `waitid` reported an error (e.g. `ECHILD` when there are no more children), the `process_wait` hook raised instead of returning the error as a `Process::Status`, so callers that expect `waitpid` to *report* "no more children" rather than raise would fail.
|
|
28
|
+
|
|
21
29
|
### v1.19.0
|
|
22
30
|
|
|
23
31
|
- 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.
|
|
@@ -57,14 +65,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
57
65
|
- Correctly handle short `io_uring_submit()` results in the `URing` selector. `io_uring_submit()` returns the number of SQEs actually accepted by the kernel and can be short (SQE prep errors, `ENOMEM`, transient `EAGAIN`); the old accounting reset `pending = 0` on any success and silently lost track of unsubmitted SQEs.
|
|
58
66
|
- Enable `IORING_SETUP_SUBMIT_ALL` (kernel 5.18+) on the `URing` selector so the kernel keeps processing the rest of an SQE batch past individual errors, reducing the frequency of short submits in practice.
|
|
59
67
|
|
|
60
|
-
### v1.15.1
|
|
61
|
-
|
|
62
|
-
- Simplify closed-IO handling in the `Select` selector: rely on Ruby 4's `rb_thread_io_close_interrupt` to wake fibers waiting on a descriptor that's been closed, removing a custom error-recovery path that could mis-attribute `IOError` / `Errno::EBADF` to the wrong waiter.
|
|
63
|
-
|
|
64
|
-
### v1.15.0
|
|
65
|
-
|
|
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.
|
|
67
|
-
|
|
68
68
|
## Contributing
|
|
69
69
|
|
|
70
70
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v1.19.2
|
|
4
|
+
|
|
5
|
+
- Use `rb_process_status_for` when available to construct `URing` `process_wait` results directly from `waitid`, avoiding the extra reap syscall previously needed to build a `Process::Status`.
|
|
6
|
+
|
|
7
|
+
## v1.19.1
|
|
8
|
+
|
|
9
|
+
- Fix `Process.waitall` / `Process.detach` under the `URing` selector: when `io_uring`'s `waitid` reported an error (e.g. `ECHILD` when there are no more children), the `process_wait` hook raised instead of returning the error as a `Process::Status`, so callers that expect `waitpid` to *report* "no more children" rather than raise would fail.
|
|
10
|
+
|
|
3
11
|
## v1.19.0
|
|
4
12
|
|
|
5
13
|
- 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.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|