io-event 1.18.0 → 1.19.1
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 +7 -2
- data/ext/io/event/selector/kqueue.c +7 -2
- data/ext/io/event/selector/selector.c +13 -1
- data/ext/io/event/selector/selector.h +10 -2
- data/ext/io/event/selector/uring.c +77 -4
- 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: ba7c5ec2b2a68b1d615e75f33a081973b1f6c28122128e21e014dd5a348118b1
|
|
4
|
+
data.tar.gz: 78435a88a40c5b6458a13887cc709fcfc386c6ed7268b382fc7f976e422aa9c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b72b4d8e4f03515215af878ffe81c7bacf34be736bf8bc642d3e12e21d78c39eed7e1ae78ebb68786cddeae07042588a202b29f7a382352ed72fa08b1673cf88
|
|
7
|
+
data.tar.gz: 780583a7ef578f08f3e9c6fdec3547ea1140de80b514c934d3f3bd56d95eeb776a07f5cd5fc6fea2c65708a7730401c172bf981dc2d4e086325123e4cb6e0ae4
|
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;
|
|
@@ -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");
|
|
@@ -24,7 +24,7 @@ static VALUE rb_Process_Status = Qnil;
|
|
|
24
24
|
|
|
25
25
|
VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags)
|
|
26
26
|
{
|
|
27
|
-
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));
|
|
28
28
|
}
|
|
29
29
|
#endif
|
|
30
30
|
|
|
@@ -80,9 +80,21 @@ static VALUE IO_Event_Selector_nonblock(VALUE class, VALUE io)
|
|
|
80
80
|
return rb_ensure(rb_yield, io, IO_Event_Selector_nonblock_ensure, (VALUE)&arguments);
|
|
81
81
|
}
|
|
82
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
|
+
|
|
83
91
|
void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
|
|
84
92
|
IO_Event_Selector_pending_interrupt_p_id = rb_intern("pending_interrupt?");
|
|
85
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
|
+
|
|
86
98
|
#ifndef HAVE_RB_IO_DESCRIPTOR
|
|
87
99
|
id_fileno = rb_intern("fileno");
|
|
88
100
|
#endif
|
|
@@ -52,13 +52,21 @@ static inline int IO_Event_Selector_pending_interrupt(void) {
|
|
|
52
52
|
int IO_Event_Selector_io_descriptor(VALUE io);
|
|
53
53
|
#endif
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// Wait for a process to change state. This blocks until the process changes state, unless `WNOHANG` is given in `flags`.
|
|
56
56
|
#ifdef HAVE_RB_PROCESS_STATUS_WAIT
|
|
57
|
-
#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)
|
|
58
58
|
#else
|
|
59
59
|
VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags);
|
|
60
60
|
#endif
|
|
61
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
|
+
|
|
62
70
|
int IO_Event_Selector_nonblock_set(int file_descriptor);
|
|
63
71
|
void IO_Event_Selector_nonblock_restore(int file_descriptor, int flags);
|
|
64
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,34 @@ 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
|
+
|
|
558
|
+
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);
|
|
559
|
+
|
|
560
|
+
if (result < 0) {
|
|
561
|
+
// 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:
|
|
562
|
+
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// 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
|
+
return IO_Event_Selector_process_status_reap(arguments->siginfo.si_pid, arguments->flags);
|
|
567
|
+
#else
|
|
518
568
|
if (arguments->waiting->result) {
|
|
519
|
-
return
|
|
569
|
+
return IO_Event_Selector_process_status_reap(arguments->pid, arguments->flags);
|
|
520
570
|
} else {
|
|
521
571
|
return Qfalse;
|
|
522
572
|
}
|
|
573
|
+
#endif
|
|
523
574
|
}
|
|
524
575
|
|
|
525
576
|
static
|
|
526
577
|
VALUE process_wait_ensure(VALUE _arguments) {
|
|
527
578
|
struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
|
|
528
579
|
|
|
580
|
+
#ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
529
581
|
close(arguments->descriptor);
|
|
582
|
+
#endif
|
|
530
583
|
|
|
531
584
|
IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
|
|
532
585
|
|
|
@@ -540,11 +593,18 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
540
593
|
pid_t pid = NUM2PIDT(_pid);
|
|
541
594
|
int flags = NUM2INT(_flags);
|
|
542
595
|
|
|
596
|
+
#ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
597
|
+
// `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:
|
|
598
|
+
if (pid <= 0) {
|
|
599
|
+
return IO_Event_Selector_process_wait(pid, flags);
|
|
600
|
+
}
|
|
601
|
+
|
|
543
602
|
int descriptor = pidfd_open(pid, 0);
|
|
544
603
|
if (descriptor < 0) {
|
|
545
604
|
rb_syserr_fail(errno, "IO_Event_Selector_URing_process_wait:pidfd_open");
|
|
546
605
|
}
|
|
547
606
|
rb_update_max_fd(descriptor);
|
|
607
|
+
#endif
|
|
548
608
|
|
|
549
609
|
struct IO_Event_Selector_URing_Waiting waiting = {
|
|
550
610
|
.fiber = fiber,
|
|
@@ -559,12 +619,25 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
|
|
|
559
619
|
.waiting = &waiting,
|
|
560
620
|
.pid = pid,
|
|
561
621
|
.flags = flags,
|
|
622
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
623
|
+
.siginfo = {0},
|
|
624
|
+
#else
|
|
562
625
|
.descriptor = descriptor,
|
|
626
|
+
#endif
|
|
563
627
|
};
|
|
564
628
|
|
|
565
|
-
if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
|
|
566
629
|
struct io_uring_sqe *sqe = io_get_sqe(selector);
|
|
630
|
+
|
|
631
|
+
#ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
|
|
632
|
+
id_t id;
|
|
633
|
+
idtype_t idtype = process_waitid_type(pid, &id);
|
|
634
|
+
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);
|
|
635
|
+
// `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
|
+
io_uring_prep_waitid(sqe, idtype, id, &process_wait_arguments.siginfo, WEXITED | WNOWAIT, 0);
|
|
637
|
+
#else
|
|
638
|
+
if (DEBUG) fprintf(stderr, "IO_Event_Selector_URing_process_wait:io_uring_prep_poll_add(%p)\n", (void*)fiber);
|
|
567
639
|
io_uring_prep_poll_add(sqe, descriptor, POLLIN|POLLHUP|POLLERR);
|
|
640
|
+
#endif
|
|
568
641
|
io_uring_sqe_set_data(sqe, completion);
|
|
569
642
|
io_uring_submit_pending(selector);
|
|
570
643
|
|
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.1
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
|
|
25
|
+
### v1.19.0
|
|
26
|
+
|
|
27
|
+
- 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.
|
|
28
|
+
- 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.
|
|
29
|
+
|
|
21
30
|
### v1.18.0
|
|
22
31
|
|
|
23
32
|
- **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
|
|
@@ -56,14 +65,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
56
65
|
|
|
57
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.
|
|
58
67
|
|
|
59
|
-
### v1.15.0
|
|
60
|
-
|
|
61
|
-
- 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.
|
|
62
|
-
|
|
63
|
-
### v1.14.4
|
|
64
|
-
|
|
65
|
-
- Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.
|
|
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.1
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
7
|
+
## v1.19.0
|
|
8
|
+
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
|
|
3
12
|
## v1.18.0
|
|
4
13
|
|
|
5
14
|
- **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
|
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.1
|
|
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
|