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.
@@ -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 | WNOHANG));
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
- // Reap a process without hanging.
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 | WNOHANG)
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;