io-event 1.19.1 → 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: ba7c5ec2b2a68b1d615e75f33a081973b1f6c28122128e21e014dd5a348118b1
4
- data.tar.gz: 78435a88a40c5b6458a13887cc709fcfc386c6ed7268b382fc7f976e422aa9c5
3
+ metadata.gz: ce297a0d80038a28c3814c5a1bb227453ad27b30cdf253cd20c9abfaed90dd59
4
+ data.tar.gz: 7017435d4b51d770524b289dedad72f39a4f166290af2767ea7a32123396f471
5
5
  SHA512:
6
- metadata.gz: b72b4d8e4f03515215af878ffe81c7bacf34be736bf8bc642d3e12e21d78c39eed7e1ae78ebb68786cddeae07042588a202b29f7a382352ed72fa08b1673cf88
7
- data.tar.gz: 780583a7ef578f08f3e9c6fdec3547ea1140de80b514c934d3f3bd56d95eeb776a07f5cd5fc6fea2c65708a7730401c172bf981dc2d4e086325123e4cb6e0ae4
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;
@@ -559,11 +582,20 @@ VALUE process_wait_transfer(VALUE _arguments) {
559
582
 
560
583
  if (result < 0) {
561
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
562
588
  return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
589
+ #endif
563
590
  }
564
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
565
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`:
566
597
  return IO_Event_Selector_process_status_reap(arguments->siginfo.si_pid, arguments->flags);
598
+ #endif
567
599
  #else
568
600
  if (arguments->waiting->result) {
569
601
  return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
@@ -632,8 +664,13 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
632
664
  id_t id;
633
665
  idtype_t idtype = process_waitid_type(pid, &id);
634
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
635
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`:
636
672
  io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED | WNOWAIT, 0);
673
+ #endif
637
674
  #else
638
675
  if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
639
676
  io_uring_prep_poll_add(sqe, descriptor, POLLIN|POLLHUP|POLLERR);
@@ -1211,8 +1248,8 @@ int select_blocking_allowed(struct __kernel_timespec *timespec) {
1211
1248
  return 0;
1212
1249
  }
1213
1250
 
1214
- #ifndef RB_NOGVL_PENDING_INTERRUPT_FAIL
1215
- // 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.
1216
1253
  if (IO_Event_Selector_pending_interrupt()) {
1217
1254
  return 0;
1218
1255
  }
@@ -1259,8 +1296,8 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
1259
1296
 
1260
1297
  arguments->result = -EINTR;
1261
1298
  selector->blocked = 1;
1262
- #ifdef RB_NOGVL_PENDING_INTERRUPT_FAIL
1263
- 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);
1264
1301
  #else
1265
1302
  rb_thread_call_without_gvl2(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
1266
1303
  #endif
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.19.1"
10
+ VERSION = "1.19.2"
11
11
  end
12
12
  end
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.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
+
21
25
  ### v1.19.1
22
26
 
23
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.
@@ -61,10 +65,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
61
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.
62
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.
63
67
 
64
- ### v1.15.1
65
-
66
- - 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.
67
-
68
68
  ## Contributing
69
69
 
70
70
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
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
+
3
7
  ## v1.19.1
4
8
 
5
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.
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.1
4
+ version: 1.19.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file