io-event 1.16.2 → 1.21.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/context/getting-started.md +31 -5
- data/ext/extconf.rb +4 -4
- data/ext/io/event/list.h +1 -1
- data/ext/io/event/selector/epoll.c +196 -97
- data/ext/io/event/selector/kqueue.c +195 -101
- data/ext/io/event/selector/selector.c +77 -1
- data/ext/io/event/selector/selector.h +24 -2
- data/ext/io/event/selector/uring.c +508 -88
- data/ext/io/event/worker_pool.c +45 -34
- data/lib/io/event/debug/selector.rb +13 -6
- data/lib/io/event/interrupt.rb +12 -7
- data/lib/io/event/selector/select.rb +108 -58
- data/lib/io/event/selector.rb +24 -9
- data/lib/io/event/timers.rb +1 -1
- data/lib/io/event/version.rb +1 -1
- data/license.md +1 -0
- data/readme.md +22 -26
- data/releases.md +50 -0
- data.tar.gz.sig +0 -0
- metadata +3 -2
- metadata.gz.sig +0 -0
|
@@ -8,6 +8,60 @@
|
|
|
8
8
|
|
|
9
9
|
static const int DEBUG = 0;
|
|
10
10
|
|
|
11
|
+
#ifndef RB_NOGVL_PENDING_INTR_FAIL
|
|
12
|
+
static ID handle_interrupt_id;
|
|
13
|
+
static VALUE signal_exception_never = Qnil;
|
|
14
|
+
|
|
15
|
+
struct IO_Event_Selector_blocking_operation_arguments {
|
|
16
|
+
void *(*function)(void *);
|
|
17
|
+
void *data;
|
|
18
|
+
rb_unblock_function_t *unblock_function;
|
|
19
|
+
void *unblock_data;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static VALUE IO_Event_Selector_blocking_operation_yield(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_argument, callback_argument)) {
|
|
23
|
+
struct IO_Event_Selector_blocking_operation_arguments *arguments = (struct IO_Event_Selector_blocking_operation_arguments *)callback_argument;
|
|
24
|
+
|
|
25
|
+
rb_thread_call_without_gvl2(arguments->function, arguments->data, arguments->unblock_function, arguments->unblock_data);
|
|
26
|
+
|
|
27
|
+
return Qnil;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static VALUE IO_Event_Selector_blocking_operation_fallback(VALUE callback_argument) {
|
|
31
|
+
return rb_block_call(rb_cThread, handle_interrupt_id, 1, &signal_exception_never, IO_Event_Selector_blocking_operation_yield, callback_argument);
|
|
32
|
+
}
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
void IO_Event_Selector_blocking_operation(struct IO_Event_Selector *selector, void *(*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *unblock_data) {
|
|
36
|
+
int state = 0;
|
|
37
|
+
selector->blocked = 1;
|
|
38
|
+
|
|
39
|
+
#ifdef RB_NOGVL_PENDING_INTR_FAIL
|
|
40
|
+
rb_nogvl(function, data, unblock_function, unblock_data, RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTR_FAIL);
|
|
41
|
+
#else
|
|
42
|
+
// `Thread.handle_interrupt` resets `pending_interrupt_queue_checked` and
|
|
43
|
+
// raises the VM interrupt flag when its mask is pushed with a non-empty
|
|
44
|
+
// pending queue. Its C block calls `rb_thread_call_without_gvl2` directly,
|
|
45
|
+
// without a Ruby safepoint between refreshing the flag and entering
|
|
46
|
+
// `unblock_function_set`. That function either observes the interrupt or
|
|
47
|
+
// installs the UBF while holding Ruby's interrupt lock.
|
|
48
|
+
//
|
|
49
|
+
// Keep signal exceptions deferred here: this fallback should prevent a
|
|
50
|
+
// stranded native wait without changing the caller's delivery timing.
|
|
51
|
+
struct IO_Event_Selector_blocking_operation_arguments arguments = {
|
|
52
|
+
.function = function,
|
|
53
|
+
.data = data,
|
|
54
|
+
.unblock_function = unblock_function,
|
|
55
|
+
.unblock_data = unblock_data,
|
|
56
|
+
};
|
|
57
|
+
rb_protect(IO_Event_Selector_blocking_operation_fallback, (VALUE)&arguments, &state);
|
|
58
|
+
#endif
|
|
59
|
+
|
|
60
|
+
selector->blocked = 0;
|
|
61
|
+
|
|
62
|
+
if (state) rb_jump_tag(state);
|
|
63
|
+
}
|
|
64
|
+
|
|
11
65
|
#ifndef HAVE_RB_IO_DESCRIPTOR
|
|
12
66
|
static ID id_fileno;
|
|
13
67
|
|
|
@@ -22,7 +76,7 @@ static VALUE rb_Process_Status = Qnil;
|
|
|
22
76
|
|
|
23
77
|
VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags)
|
|
24
78
|
{
|
|
25
|
-
return rb_funcall(rb_Process_Status, id_wait, 2, PIDT2NUM(pid), INT2NUM(flags
|
|
79
|
+
return rb_funcall(rb_Process_Status, id_wait, 2, PIDT2NUM(pid), INT2NUM(flags));
|
|
26
80
|
}
|
|
27
81
|
#endif
|
|
28
82
|
|
|
@@ -78,7 +132,28 @@ static VALUE IO_Event_Selector_nonblock(VALUE class, VALUE io)
|
|
|
78
132
|
return rb_ensure(rb_yield, io, IO_Event_Selector_nonblock_ensure, (VALUE)&arguments);
|
|
79
133
|
}
|
|
80
134
|
|
|
135
|
+
static VALUE rb_IO_Event_Selector = Qnil;
|
|
136
|
+
static ID id_process_wait;
|
|
137
|
+
|
|
138
|
+
// 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.
|
|
139
|
+
VALUE IO_Event_Selector_process_wait(rb_pid_t pid, int flags) {
|
|
140
|
+
return rb_funcall(rb_IO_Event_Selector, id_process_wait, 2, PIDT2NUM(pid), INT2NUM(flags));
|
|
141
|
+
}
|
|
142
|
+
|
|
81
143
|
void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
|
|
144
|
+
#ifndef RB_NOGVL_PENDING_INTR_FAIL
|
|
145
|
+
handle_interrupt_id = rb_intern("handle_interrupt");
|
|
146
|
+
signal_exception_never = rb_hash_new();
|
|
147
|
+
rb_hash_aset(signal_exception_never, rb_eSignal, ID2SYM(rb_intern("never")));
|
|
148
|
+
rb_funcall(signal_exception_never, rb_intern("compare_by_identity"), 0);
|
|
149
|
+
rb_obj_freeze(signal_exception_never);
|
|
150
|
+
rb_gc_register_mark_object(signal_exception_never);
|
|
151
|
+
#endif
|
|
152
|
+
|
|
153
|
+
rb_IO_Event_Selector = IO_Event_Selector;
|
|
154
|
+
rb_gc_register_mark_object(rb_IO_Event_Selector);
|
|
155
|
+
id_process_wait = rb_intern("process_wait");
|
|
156
|
+
|
|
82
157
|
#ifndef HAVE_RB_IO_DESCRIPTOR
|
|
83
158
|
id_fileno = rb_intern("fileno");
|
|
84
159
|
#endif
|
|
@@ -98,6 +173,7 @@ void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self,
|
|
|
98
173
|
|
|
99
174
|
backend->waiting = NULL;
|
|
100
175
|
backend->ready = NULL;
|
|
176
|
+
backend->blocked = 0;
|
|
101
177
|
}
|
|
102
178
|
|
|
103
179
|
VALUE IO_Event_Selector_loop_resume(struct IO_Event_Selector *backend, VALUE fiber, int argc, VALUE *argv) {
|
|
@@ -40,19 +40,37 @@ static inline int IO_Event_try_again(int error) {
|
|
|
40
40
|
return error == EAGAIN || error == EWOULDBLOCK;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
static inline int IO_Event_Selector_valid_buffer_range(size_t size, size_t offset, size_t length) {
|
|
44
|
+
return offset <= size && length <= size - offset;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Enter a blocking region without the GVL. On Rubies without
|
|
48
|
+
// `RB_NOGVL_PENDING_INTR_FAIL`, refresh pending-interrupt state immediately
|
|
49
|
+
// before releasing the GVL so a signal cannot be stranded in the queue.
|
|
50
|
+
struct IO_Event_Selector;
|
|
51
|
+
void IO_Event_Selector_blocking_operation(struct IO_Event_Selector *selector, void *(*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *unblock_data);
|
|
52
|
+
|
|
43
53
|
#ifdef HAVE_RB_IO_DESCRIPTOR
|
|
44
54
|
#define IO_Event_Selector_io_descriptor(io) rb_io_descriptor(io)
|
|
45
55
|
#else
|
|
46
56
|
int IO_Event_Selector_io_descriptor(VALUE io);
|
|
47
57
|
#endif
|
|
48
58
|
|
|
49
|
-
//
|
|
59
|
+
// Wait for a process to change state. This blocks until the process changes state, unless `WNOHANG` is given in `flags`.
|
|
50
60
|
#ifdef HAVE_RB_PROCESS_STATUS_WAIT
|
|
51
|
-
#define IO_Event_Selector_process_status_wait(pid, flags) rb_process_status_wait(pid, flags
|
|
61
|
+
#define IO_Event_Selector_process_status_wait(pid, flags) rb_process_status_wait(pid, flags)
|
|
52
62
|
#else
|
|
53
63
|
VALUE IO_Event_Selector_process_status_wait(rb_pid_t pid, int flags);
|
|
54
64
|
#endif
|
|
55
65
|
|
|
66
|
+
// Reap a process that is known to have changed state (e.g. after a readiness event), without blocking.
|
|
67
|
+
static inline VALUE IO_Event_Selector_process_status_reap(rb_pid_t pid, int flags) {
|
|
68
|
+
return IO_Event_Selector_process_status_wait(pid, flags | WNOHANG);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 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.
|
|
72
|
+
VALUE IO_Event_Selector_process_wait(rb_pid_t pid, int flags);
|
|
73
|
+
|
|
56
74
|
int IO_Event_Selector_nonblock_set(int file_descriptor);
|
|
57
75
|
void IO_Event_Selector_nonblock_restore(int file_descriptor, int flags);
|
|
58
76
|
|
|
@@ -76,6 +94,10 @@ struct IO_Event_Selector {
|
|
|
76
94
|
VALUE self;
|
|
77
95
|
VALUE loop;
|
|
78
96
|
|
|
97
|
+
// Whether the selector is currently blocked in a system call without the GVL.
|
|
98
|
+
// Used by wakeup() to determine if an interrupt signal is needed.
|
|
99
|
+
int blocked;
|
|
100
|
+
|
|
79
101
|
// The ready queue is a list of fibers that are ready to be resumed from the event loop fiber.
|
|
80
102
|
// Append to waiting (front/head of queue).
|
|
81
103
|
struct IO_Event_Selector_Queue *waiting;
|