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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4e37070c965d89037b3fac95c4b5de378273050ee28eb397a954c5fce84a6ae
4
- data.tar.gz: 7571ee0edd25008cafe5cc9a81488969d5d269ec1e53793fa7cca393bbfd754c
3
+ metadata.gz: ce297a0d80038a28c3814c5a1bb227453ad27b30cdf253cd20c9abfaed90dd59
4
+ data.tar.gz: 7017435d4b51d770524b289dedad72f39a4f166290af2767ea7a32123396f471
5
5
  SHA512:
6
- metadata.gz: 22cb0db38d55f3710fc36469c2f2e1162fd69c69c228a9be2c4310f83dbc5304c8cb4194fc7fe2ca4b4316f906ff17341bcceeb24ed1cf9ebe70cd076bb05ca2
7
- data.tar.gz: d7e4a07d6acf309898b3df66e8e6b2b21f09af99a7f21ac03f30c68ad32024a1766320159f350c450df18aa46c911951c87c6f38abd06c55c9635f7eb1c86831
6
+ metadata.gz: d214fc9c5cd13f627e1f1be6676e1b86daa013ee27d360323df85b4f7f33d267f2f61bd2db7f8dcbbe46f8b3e448d0aca995a1b3f23cd8275048066f3c03ef05
7
+ data.tar.gz: 6b7f4d739d1b5bb13648575fcb0d57b2dc5965b29ae136f91cd03a200cfceb08a191476f528a487807f37b178b8e1a25021dba9b00929cc68ca7166cf081c034
checksums.yaml.gz.sig CHANGED
Binary file
data/ext/extconf.rb CHANGED
@@ -51,6 +51,7 @@ have_header("sys/eventfd.h")
51
51
  $srcs << "io/event/interrupt.c"
52
52
 
53
53
  have_func("rb_io_descriptor")
54
+ have_func("rb_process_status_for")
54
55
  have_func("&rb_process_status_wait")
55
56
  have_func("rb_fiber_current")
56
57
  have_func("&rb_fiber_raise")
@@ -847,8 +847,8 @@ int select_blocking_allowed(struct timespec * timespec) {
847
847
  return 0;
848
848
  }
849
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.
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 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);
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 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.
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 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);
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 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.
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 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);
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
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.19.0"
10
+ VERSION = "1.19.2"
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.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
@@ -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.19.0
4
+ version: 1.19.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file