io-event 1.8.4 → 1.9.0

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.
@@ -157,7 +157,7 @@ size_t IO_Event_Selector_EPoll_Type_size(const void *_selector)
157
157
  }
158
158
 
159
159
  static const rb_data_type_t IO_Event_Selector_EPoll_Type = {
160
- .wrap_struct_name = "IO_Event::Backend::EPoll",
160
+ .wrap_struct_name = "IO::Event::Backend::EPoll",
161
161
  .function = {
162
162
  .dmark = IO_Event_Selector_EPoll_Type_mark,
163
163
  .dcompact = IO_Event_Selector_EPoll_Type_compact,
@@ -394,7 +394,7 @@ VALUE IO_Event_Selector_EPoll_transfer(VALUE self)
394
394
  struct IO_Event_Selector_EPoll *selector = NULL;
395
395
  TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, selector);
396
396
 
397
- return IO_Event_Selector_fiber_transfer(selector->backend.loop, 0, NULL);
397
+ return IO_Event_Selector_loop_yield(&selector->backend);
398
398
  }
399
399
 
400
400
  VALUE IO_Event_Selector_EPoll_resume(int argc, VALUE *argv, VALUE self)
@@ -450,7 +450,7 @@ static
450
450
  VALUE process_wait_transfer(VALUE _arguments) {
451
451
  struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
452
452
 
453
- IO_Event_Selector_fiber_transfer(arguments->selector->backend.loop, 0, NULL);
453
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
454
454
 
455
455
  if (arguments->waiting->ready) {
456
456
  return IO_Event_Selector_process_status_wait(arguments->pid, arguments->flags);
@@ -538,7 +538,7 @@ static
538
538
  VALUE io_wait_transfer(VALUE _arguments) {
539
539
  struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
540
540
 
541
- IO_Event_Selector_fiber_transfer(arguments->selector->backend.loop, 0, NULL);
541
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
542
542
 
543
543
  if (arguments->waiting->ready) {
544
544
  return RB_INT2NUM(arguments->waiting->ready);
@@ -925,7 +925,7 @@ int IO_Event_Selector_EPoll_handle(struct IO_Event_Selector_EPoll *selector, con
925
925
 
926
926
  // Resume the fiber:
927
927
  waiting->ready = matching_events;
928
- IO_Event_Selector_fiber_transfer_user(waiting->fiber, 0, NULL);
928
+ IO_Event_Selector_loop_resume(&selector->backend, waiting->fiber, 0, NULL);
929
929
 
930
930
  node = saved->tail;
931
931
  IO_Event_List_pop(saved);
@@ -156,7 +156,7 @@ size_t IO_Event_Selector_KQueue_Type_size(const void *_selector)
156
156
  }
157
157
 
158
158
  static const rb_data_type_t IO_Event_Selector_KQueue_Type = {
159
- .wrap_struct_name = "IO_Event::Backend::KQueue",
159
+ .wrap_struct_name = "IO::Event::Backend::KQueue",
160
160
  .function = {
161
161
  .dmark = IO_Event_Selector_KQueue_Type_mark,
162
162
  .dcompact = IO_Event_Selector_KQueue_Type_compact,
@@ -381,7 +381,7 @@ VALUE IO_Event_Selector_KQueue_transfer(VALUE self)
381
381
  struct IO_Event_Selector_KQueue *selector = NULL;
382
382
  TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
383
383
 
384
- return IO_Event_Selector_fiber_transfer(selector->backend.loop, 0, NULL);
384
+ return IO_Event_Selector_loop_yield(&selector->backend);
385
385
  }
386
386
 
387
387
  VALUE IO_Event_Selector_KQueue_resume(int argc, VALUE *argv, VALUE self)
@@ -455,7 +455,7 @@ static
455
455
  VALUE process_wait_transfer(VALUE _arguments) {
456
456
  struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
457
457
 
458
- IO_Event_Selector_fiber_transfer(arguments->selector->backend.loop, 0, NULL);
458
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
459
459
 
460
460
  if (arguments->waiting->ready) {
461
461
  process_prewait(arguments->pid);
@@ -531,7 +531,7 @@ static
531
531
  VALUE io_wait_transfer(VALUE _arguments) {
532
532
  struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
533
533
 
534
- IO_Event_Selector_fiber_transfer(arguments->selector->backend.loop, 0, NULL);
534
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
535
535
 
536
536
  if (arguments->waiting->ready) {
537
537
  return RB_INT2NUM(arguments->waiting->ready);
@@ -897,7 +897,7 @@ int IO_Event_Selector_KQueue_handle(struct IO_Event_Selector_KQueue *selector, u
897
897
  IO_Event_List_append(node, saved);
898
898
 
899
899
  waiting->ready = matching_events;
900
- IO_Event_Selector_fiber_transfer_user(waiting->fiber, 0, NULL);
900
+ IO_Event_Selector_loop_resume(&selector->backend, waiting->fiber, 0, NULL);
901
901
 
902
902
  node = saved->tail;
903
903
  IO_Event_List_pop(saved);
@@ -2,76 +2,12 @@
2
2
  // Copyright, 2021-2025, by Samuel Williams.
3
3
 
4
4
  #include "selector.h"
5
- #include "../profile.h"
6
5
 
7
6
  #include <fcntl.h>
8
7
  #include <stdlib.h>
9
8
 
10
9
  static const int DEBUG = 0;
11
10
 
12
- static ID id_transfer, id_alive_p;
13
-
14
- static float IO_Event_Selector_stall_log_threshold = 0;
15
- static int IO_Event_Selector_stall_log_profile = 0;
16
-
17
- VALUE IO_Event_Selector_fiber_transfer(VALUE fiber, int argc, VALUE *argv) {
18
- // TODO Consider introducing something like `rb_fiber_scheduler_transfer(...)`.
19
- #ifdef HAVE__RB_FIBER_TRANSFER
20
- if (RTEST(rb_obj_is_fiber(fiber))) {
21
- if (RTEST(rb_fiber_alive_p(fiber))) {
22
- return rb_fiber_transfer(fiber, argc, argv);
23
- }
24
-
25
- return Qnil;
26
- }
27
- #endif
28
- if (RTEST(rb_funcall(fiber, id_alive_p, 0))) {
29
- return rb_funcallv(fiber, id_transfer, argc, argv);
30
- }
31
-
32
- return Qnil;
33
- }
34
-
35
- VALUE IO_Event_Selector_fiber_transfer_user(VALUE fiber, int argc, VALUE *argv) {
36
- // Bypass if the threshold is not set:
37
- if (IO_Event_Selector_stall_log_threshold == 0) {
38
- return IO_Event_Selector_fiber_transfer(fiber, argc, argv);
39
- }
40
-
41
- VALUE profile = IO_Event_Profile_allocate(IO_Event_Profile);
42
-
43
- IO_Event_Profile_start(profile, IO_Event_Selector_stall_log_profile);
44
-
45
- // Transfer control to the fiber:
46
- VALUE result = IO_Event_Selector_fiber_transfer(fiber, argc, argv);
47
-
48
- IO_Event_Profile_stop(profile);
49
-
50
- float duration = IO_Event_Profile_duration(profile);
51
- if (duration > IO_Event_Selector_stall_log_threshold) {
52
- IO_Event_Profile_print(profile, stderr);
53
- }
54
-
55
- return result;
56
- }
57
-
58
- #ifndef HAVE__RB_FIBER_RAISE
59
- static ID id_raise;
60
-
61
- VALUE IO_Event_Selector_fiber_raise(VALUE fiber, int argc, VALUE *argv) {
62
- return rb_funcallv(fiber, id_raise, argc, argv);
63
- }
64
- #endif
65
-
66
- #ifndef HAVE_RB_FIBER_CURRENT
67
- static ID id_current;
68
-
69
- static VALUE rb_fiber_current() {
70
- return rb_funcall(rb_cFiber, id_current, 0);
71
- }
72
- #endif
73
-
74
-
75
11
  #ifndef HAVE_RB_IO_DESCRIPTOR
76
12
  static ID id_fileno;
77
13
 
@@ -148,17 +84,6 @@ static VALUE IO_Event_Selector_nonblock(VALUE class, VALUE io)
148
84
  }
149
85
 
150
86
  void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
151
- id_transfer = rb_intern("transfer");
152
- id_alive_p = rb_intern("alive?");
153
-
154
- #ifndef HAVE__RB_FIBER_RAISE
155
- id_raise = rb_intern("raise");
156
- #endif
157
-
158
- #ifndef HAVE_RB_FIBER_CURRENT
159
- id_current = rb_intern("current");
160
- #endif
161
-
162
87
  #ifndef HAVE_RB_IO_DESCRIPTOR
163
88
  id_fileno = rb_intern("fileno");
164
89
  #endif
@@ -170,31 +95,25 @@ void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
170
95
  #endif
171
96
 
172
97
  rb_define_singleton_method(IO_Event_Selector, "nonblock", IO_Event_Selector_nonblock, 1);
98
+ }
99
+
100
+ void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, VALUE loop) {
101
+ RB_OBJ_WRITE(self, &backend->self, self);
102
+ RB_OBJ_WRITE(self, &backend->loop, loop);
173
103
 
174
- // Extract the stall log threshold if specified:
175
-
176
- char *stall_log_threshold = getenv("IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD");
177
- // Can be true, false or a floating point time in seconds:
178
-
179
- if (stall_log_threshold) {
180
- if (strcmp(stall_log_threshold, "true") == 0) {
181
- IO_Event_Selector_stall_log_threshold = 0.01;
182
- } else if (strcmp(stall_log_threshold, "false") == 0) {
183
- IO_Event_Selector_stall_log_threshold = 0;
184
- } else {
185
- IO_Event_Selector_stall_log_threshold = strtof(stall_log_threshold, NULL);
186
- }
187
-
188
- if (DEBUG) fprintf(stderr, "IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD = %.3f\n", IO_Event_Selector_stall_log_threshold);
189
- }
190
-
191
- char *stall_log_profile = getenv("IO_EVENT_SELECTOR_STALL_LOG_PROFILE");
192
-
193
- if (stall_log_profile) {
194
- if (strcmp(stall_log_profile, "true") == 0) {
195
- IO_Event_Selector_stall_log_profile = 1;
196
- }
197
- }
104
+ backend->waiting = NULL;
105
+ backend->ready = NULL;
106
+ }
107
+
108
+ VALUE IO_Event_Selector_loop_resume(struct IO_Event_Selector *backend, VALUE fiber, int argc, VALUE *argv) {
109
+ return IO_Event_Fiber_transfer(fiber, argc, argv);
110
+ }
111
+
112
+ VALUE IO_Event_Selector_loop_yield(struct IO_Event_Selector *backend)
113
+ {
114
+ // TODO Why is this assertion failing in async?
115
+ // RUBY_ASSERT(backend->loop != IO_Event_Fiber_current());
116
+ return IO_Event_Fiber_transfer(backend->loop, 0, NULL);
198
117
  }
199
118
 
200
119
  struct wait_and_transfer_arguments {
@@ -248,7 +167,7 @@ static VALUE wait_and_transfer(VALUE _arguments) {
248
167
  int argc = arguments->argc - 1;
249
168
  VALUE *argv = arguments->argv + 1;
250
169
 
251
- return IO_Event_Selector_fiber_transfer_user(fiber, argc, argv);
170
+ return IO_Event_Selector_loop_resume(arguments->backend, fiber, argc, argv);
252
171
  }
253
172
 
254
173
  static VALUE wait_and_transfer_ensure(VALUE _arguments) {
@@ -267,7 +186,7 @@ VALUE IO_Event_Selector_resume(struct IO_Event_Selector *backend, int argc, VALU
267
186
  .head = NULL,
268
187
  .tail = NULL,
269
188
  .flags = IO_EVENT_SELECTOR_QUEUE_FIBER,
270
- .fiber = rb_fiber_current()
189
+ .fiber = IO_Event_Fiber_current()
271
190
  };
272
191
 
273
192
  RB_OBJ_WRITTEN(backend->self, Qundef, waiting.fiber);
@@ -291,7 +210,7 @@ static VALUE wait_and_raise(VALUE _arguments) {
291
210
  int argc = arguments->argc - 1;
292
211
  VALUE *argv = arguments->argv + 1;
293
212
 
294
- return IO_Event_Selector_fiber_raise(fiber, argc, argv);
213
+ return IO_Event_Fiber_raise(fiber, argc, argv);
295
214
  }
296
215
 
297
216
  VALUE IO_Event_Selector_raise(struct IO_Event_Selector *backend, int argc, VALUE *argv)
@@ -302,7 +221,7 @@ VALUE IO_Event_Selector_raise(struct IO_Event_Selector *backend, int argc, VALUE
302
221
  .head = NULL,
303
222
  .tail = NULL,
304
223
  .flags = IO_EVENT_SELECTOR_QUEUE_FIBER,
305
- .fiber = rb_fiber_current()
224
+ .fiber = IO_Event_Fiber_current()
306
225
  };
307
226
 
308
227
  RB_OBJ_WRITTEN(backend->self, Qundef, waiting.fiber);
@@ -350,7 +269,7 @@ void IO_Event_Selector_ready_pop(struct IO_Event_Selector *backend, struct IO_Ev
350
269
  rb_raise(rb_eRuntimeError, "Unknown queue type!");
351
270
  }
352
271
 
353
- IO_Event_Selector_fiber_transfer_user(fiber, 0, NULL);
272
+ IO_Event_Selector_loop_resume(backend, fiber, 0, NULL);
354
273
  }
355
274
 
356
275
  int IO_Event_Selector_ready_flush(struct IO_Event_Selector *backend)
@@ -8,6 +8,7 @@
8
8
  #include <ruby/io.h>
9
9
 
10
10
  #include "../time.h"
11
+ #include "../fiber.h"
11
12
 
12
13
  #ifdef HAVE_RUBY_IO_BUFFER_H
13
14
  #include <ruby/io/buffer.h>
@@ -39,17 +40,6 @@ static inline int IO_Event_try_again(int error) {
39
40
  return error == EAGAIN || error == EWOULDBLOCK;
40
41
  }
41
42
 
42
- VALUE IO_Event_Selector_fiber_transfer(VALUE fiber, int argc, VALUE *argv);
43
-
44
- // Specifically for transferring control to a user fiber.
45
- VALUE IO_Event_Selector_fiber_transfer_user(VALUE fiber, int argc, VALUE *argv);
46
-
47
- #ifdef HAVE__RB_FIBER_RAISE
48
- #define IO_Event_Selector_fiber_raise(fiber, argc, argv) rb_fiber_raise(fiber, argc, argv)
49
- #else
50
- VALUE IO_Event_Selector_fiber_raise(VALUE fiber, int argc, VALUE *argv);
51
- #endif
52
-
53
43
  #ifdef HAVE_RB_IO_DESCRIPTOR
54
44
  #define IO_Event_Selector_io_descriptor(io) rb_io_descriptor(io)
55
45
  #else
@@ -80,23 +70,20 @@ struct IO_Event_Selector_Queue {
80
70
  VALUE fiber;
81
71
  };
82
72
 
73
+ // The internal state of the event selector.
74
+ // The event selector is responsible for managing the scheduling of fibers, as well as selecting for events.
83
75
  struct IO_Event_Selector {
84
76
  VALUE self;
85
77
  VALUE loop;
86
78
 
79
+ // The ready queue is a list of fibers that are ready to be resumed from the event loop fiber.
87
80
  // Append to waiting (front/head of queue).
88
81
  struct IO_Event_Selector_Queue *waiting;
89
82
  // Process from ready (back/tail of queue).
90
83
  struct IO_Event_Selector_Queue *ready;
91
84
  };
92
85
 
93
- static inline
94
- void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, VALUE loop) {
95
- RB_OBJ_WRITE(self, &backend->self, self);
96
- RB_OBJ_WRITE(self, &backend->loop, loop);
97
- backend->waiting = NULL;
98
- backend->ready = NULL;
99
- }
86
+ void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, VALUE loop);
100
87
 
101
88
  static inline
102
89
  void IO_Event_Selector_mark(struct IO_Event_Selector *backend) {
@@ -123,16 +110,48 @@ void IO_Event_Selector_compact(struct IO_Event_Selector *backend) {
123
110
  }
124
111
  }
125
112
 
113
+ // Transfer control from the event loop to a user fiber.
114
+ // This is used to transfer control to a user fiber when it may proceed.
115
+ // Strictly speaking, it's not a scheduling operation (does not schedule the current fiber).
116
+ VALUE IO_Event_Selector_loop_resume(struct IO_Event_Selector *backend, VALUE fiber, int argc, VALUE *argv);
117
+
118
+ // Transfer from a user fiber back to the event loop.
119
+ // This is used to transfer control back to the event loop in order to wait for events.
120
+ // Strictly speaking, it's not a scheduling operation (does not schedule the current fiber).
121
+ VALUE IO_Event_Selector_loop_yield(struct IO_Event_Selector *backend);
122
+
123
+ // Resume a specific fiber. This is a scheduling operation.
124
+ // The first argument is the fiber, the rest are the arguments to the resume.
125
+ //
126
+ // The implementation has two possible strategies:
127
+ // 1. Add the current fiber to the ready queue and transfer control to the target fiber.
128
+ // 2. Schedule the target fiber to be resumed by the event loop later on.
129
+ //
130
+ // We currently only implement the first strategy.
126
131
  VALUE IO_Event_Selector_resume(struct IO_Event_Selector *backend, int argc, VALUE *argv);
132
+
133
+ // Raise an exception on a specific fiber.
134
+ // The first argument is the fiber, the rest are the arguments to the exception.
135
+ //
136
+ // The implementation has two possible strategies:
137
+ // 1. Add the current fiber to the ready queue and transfer control to the target fiber.
138
+ // 2. Schedule the target fiber to be resumed by the event loop with an exception later on.
139
+ //
140
+ // We currently only implement the first strategy.
127
141
  VALUE IO_Event_Selector_raise(struct IO_Event_Selector *backend, int argc, VALUE *argv);
128
142
 
143
+ // Yield control to the event loop. This is a scheduling operation.
144
+ //
145
+ // The implementation adds the current fiber to the ready queue and transfers control to the event loop.
129
146
  static inline
130
147
  VALUE IO_Event_Selector_yield(struct IO_Event_Selector *backend)
131
148
  {
132
149
  return IO_Event_Selector_resume(backend, 1, &backend->loop);
133
150
  }
134
151
 
135
- // Append to the ready queue.
152
+ // Append a specific fiber to the ready queue.
153
+ // The fiber can be an actual fiber or an object that responds to `alive?` and `transfer`.
154
+ // The implementation will transfer control to the fiber later on.
136
155
  void IO_Event_Selector_ready_push(struct IO_Event_Selector *backend, VALUE fiber);
137
156
 
138
157
  // Flush the ready queue by transferring control one at a time.
@@ -127,7 +127,7 @@ size_t IO_Event_Selector_URing_Type_size(const void *_selector)
127
127
  }
128
128
 
129
129
  static const rb_data_type_t IO_Event_Selector_URing_Type = {
130
- .wrap_struct_name = "IO_Event::Backend::URing",
130
+ .wrap_struct_name = "IO::Event::Backend::URing",
131
131
  .function = {
132
132
  .dmark = IO_Event_Selector_URing_Type_mark,
133
133
  .dcompact = IO_Event_Selector_URing_Type_compact,
@@ -277,7 +277,7 @@ VALUE IO_Event_Selector_URing_transfer(VALUE self)
277
277
  struct IO_Event_Selector_URing *selector = NULL;
278
278
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
279
279
 
280
- return IO_Event_Selector_fiber_transfer(selector->backend.loop, 0, NULL);
280
+ return IO_Event_Selector_loop_yield(&selector->backend);
281
281
  }
282
282
 
283
283
  VALUE IO_Event_Selector_URing_resume(int argc, VALUE *argv, VALUE self)
@@ -433,7 +433,7 @@ static
433
433
  VALUE process_wait_transfer(VALUE _arguments) {
434
434
  struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
435
435
 
436
- IO_Event_Selector_fiber_transfer(arguments->selector->backend.loop, 0, NULL);
436
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
437
437
 
438
438
  if (arguments->waiting->result) {
439
439
  return IO_Event_Selector_process_status_wait(arguments->pid, arguments->flags);
@@ -548,7 +548,7 @@ VALUE io_wait_transfer(VALUE _arguments) {
548
548
  struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
549
549
  struct IO_Event_Selector_URing *selector = arguments->selector;
550
550
 
551
- IO_Event_Selector_fiber_transfer(selector->backend.loop, 0, NULL);
551
+ IO_Event_Selector_loop_yield(&selector->backend);
552
552
 
553
553
  if (DEBUG) fprintf(stderr, "io_wait_transfer:waiting=%p, result=%d\n", (void*)arguments->waiting, arguments->waiting->result);
554
554
 
@@ -638,7 +638,7 @@ io_read_submit(VALUE _arguments)
638
638
  io_uring_sqe_set_data(sqe, arguments->waiting->completion);
639
639
  io_uring_submit_now(selector);
640
640
 
641
- IO_Event_Selector_fiber_transfer(selector->backend.loop, 0, NULL);
641
+ IO_Event_Selector_loop_yield(&selector->backend);
642
642
 
643
643
  return RB_INT2NUM(arguments->waiting->result);
644
644
  }
@@ -802,7 +802,7 @@ io_write_submit(VALUE _argument)
802
802
  io_uring_sqe_set_data(sqe, arguments->waiting->completion);
803
803
  io_uring_submit_pending(selector);
804
804
 
805
- IO_Event_Selector_fiber_transfer(selector->backend.loop, 0, NULL);
805
+ IO_Event_Selector_loop_yield(&selector->backend);
806
806
 
807
807
  return RB_INT2NUM(arguments->waiting->result);
808
808
  }
@@ -1086,7 +1086,7 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1086
1086
  if (waiting && waiting->fiber) {
1087
1087
  assert(waiting->result != -ECANCELED);
1088
1088
 
1089
- IO_Event_Selector_fiber_transfer_user(waiting->fiber, 0, NULL);
1089
+ IO_Event_Selector_loop_resume(&selector->backend, waiting->fiber, 0, NULL);
1090
1090
  }
1091
1091
  }
1092
1092
 
data/ext/io/event/time.c CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  #include "time.h"
5
5
 
6
- void IO_Event_Time_elapsed(struct timespec* start, struct timespec* stop, struct timespec *duration)
6
+ void IO_Event_Time_elapsed(const struct timespec* start, const struct timespec* stop, struct timespec *duration)
7
7
  {
8
8
  if ((stop->tv_nsec - start->tv_nsec) < 0) {
9
9
  duration->tv_sec = stop->tv_sec - start->tv_sec - 1;
@@ -14,7 +14,7 @@ void IO_Event_Time_elapsed(struct timespec* start, struct timespec* stop, struct
14
14
  }
15
15
  }
16
16
 
17
- float IO_Event_Time_duration(struct timespec *duration)
17
+ float IO_Event_Time_duration(const struct timespec *duration)
18
18
  {
19
19
  return duration->tv_sec + duration->tv_nsec / 1000000000.0;
20
20
  }
@@ -23,6 +23,13 @@ void IO_Event_Time_current(struct timespec *time) {
23
23
  clock_gettime(CLOCK_MONOTONIC, time);
24
24
  }
25
25
 
26
- float IO_Event_Time_proportion(struct timespec *duration, struct timespec *total_duration) {
26
+ float IO_Event_Time_proportion(const struct timespec *duration, const struct timespec *total_duration) {
27
27
  return IO_Event_Time_duration(duration) / IO_Event_Time_duration(total_duration);
28
28
  }
29
+
30
+ float IO_Event_Time_delta(const struct timespec *start, const struct timespec *stop) {
31
+ struct timespec duration;
32
+ IO_Event_Time_elapsed(start, stop, &duration);
33
+
34
+ return IO_Event_Time_duration(&duration);
35
+ }
data/ext/io/event/time.h CHANGED
@@ -6,11 +6,12 @@
6
6
  #include <ruby.h>
7
7
  #include <time.h>
8
8
 
9
- void IO_Event_Time_elapsed(struct timespec* start, struct timespec* stop, struct timespec *duration);
10
- float IO_Event_Time_duration(struct timespec *duration);
9
+ void IO_Event_Time_elapsed(const struct timespec* start, const struct timespec* stop, struct timespec *duration);
10
+ float IO_Event_Time_duration(const struct timespec *duration);
11
11
  void IO_Event_Time_current(struct timespec *time);
12
12
 
13
- float IO_Event_Time_proportion(struct timespec *duration, struct timespec *total_duration);
13
+ float IO_Event_Time_delta(const struct timespec *start, const struct timespec *stop);
14
+ float IO_Event_Time_proportion(const struct timespec *duration, const struct timespec *total_duration);
14
15
 
15
16
  #define IO_EVENT_TIME_PRINTF_TIMESPEC "%.3g"
16
17
  #define IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(ts) ((double)(ts).tv_sec + (ts).tv_nsec / 1e9)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ begin
7
+ require "IO_Event"
8
+ rescue LoadError => error
9
+ warn "Could not load native event selector: #{error}"
10
+ require_relative "selector/nonblock"
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "native"
7
+
8
+ module IO::Event
9
+ unless self.const_defined?(:Profiler)
10
+ module Profiler
11
+ # The default profiler, if the platform supports it.
12
+ # Use `IO_EVENT_PROFILER=true` to enable it.
13
+ def self.default
14
+ nil
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2025, by Samuel Williams.
5
5
 
6
6
  # @namespace
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.8.4"
10
+ VERSION = "1.9.0"
11
11
  end
12
12
  end
data/lib/io/event.rb CHANGED
@@ -1,15 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "event/version"
7
7
  require_relative "event/selector"
8
8
  require_relative "event/timers"
9
-
10
- begin
11
- require "IO_Event"
12
- rescue LoadError => error
13
- warn "Could not load native event selector: #{error}"
14
- require_relative "event/selector/nonblock"
15
- end
9
+ require_relative "event/native"
data/license.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # MIT License
2
2
 
3
3
  Copyright, 2021, by Wander Hillen.
4
- Copyright, 2021-2024, by Samuel Williams.
4
+ Copyright, 2021-2025, by Samuel Williams.
5
5
  Copyright, 2021, by Delton Ding.
6
6
  Copyright, 2021-2024, by Benoit Daloze.
7
7
  Copyright, 2022, by Alex Matchneer.
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.9.0
22
+
23
+ - [Improved `IO::Event::Profiler` for detecting stalls.](https://socketry.github.io/io-event/releases/index#improved-io::event::profiler-for-detecting-stalls.)
24
+
21
25
  ### v1.8.0
22
26
 
23
27
  - [Detecting fibers that are stalling the event loop.](https://socketry.github.io/io-event/releases/index#detecting-fibers-that-are-stalling-the-event-loop.)
data/releases.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Releases
2
2
 
3
+ ## v1.9.0
4
+
5
+ ### Improved `IO::Event::Profiler` for detecting stalls.
6
+
7
+ A new `IO::Event::Profiler` class has been added to help detect stalls in the event loop. The previous approach was insufficient to detect all possible stalls. This new approach uses the `RUBY_EVENT_FIBER_SWITCH` event to track context switching by the scheduler, and can detect stalls no matter how they occur.
8
+
9
+ ``` ruby
10
+ profiler = IO::Event::Profiler.new
11
+
12
+ profiler.start
13
+
14
+ Fiber.new do
15
+ sleep 1.0
16
+ end.transfer
17
+
18
+ profiler.stop
19
+ ```
20
+
21
+ A default profiler is exposed using `IO::Event::Profiler.default` which is controlled by the following environment variables:
22
+
23
+ - `IO_EVENT_PROFILER=true` - Enable the profiler, otherwise `IO::Event::Profiler.default` will return `nil`.
24
+ - `IO_EVENT_PROFILER_LOG_THRESHOLD` - Specify the threshold in seconds for logging a stall. Defaults to `0.01`.
25
+ - `IO_EVENT_PROFILER_TRACK_CALLS` - Track the method call for each event, in order to log specifically which method is causing the stall. Defaults to `true`.
26
+
27
+ The previous environment variables `IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD` and `IO_EVENT_SELECTOR_STALL_LOG` no longer have any effect.
28
+
3
29
  ## v1.8.0
4
30
 
5
31
  ### Detecting fibers that are stalling the event loop.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.4
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Math Ieu
9
9
  - Wander Hillen
10
+ - Jean Boussier
10
11
  - Benoit Daloze
11
12
  - Bruno Sutic
12
13
  - Alex Matchneer
13
14
  - Anthony Ross
14
15
  - Delton Ding
15
16
  - Pavel Rosický
17
+ - Shizuo Fujita
16
18
  bindir: bin
17
19
  cert_chain:
18
20
  - |
@@ -44,7 +46,7 @@ cert_chain:
44
46
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
45
47
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
46
48
  -----END CERTIFICATE-----
47
- date: 2025-02-05 00:00:00.000000000 Z
49
+ date: 2025-02-10 00:00:00.000000000 Z
48
50
  dependencies: []
49
51
  executables: []
50
52
  extensions:
@@ -56,11 +58,13 @@ files:
56
58
  - ext/io/event/array.h
57
59
  - ext/io/event/event.c
58
60
  - ext/io/event/event.h
61
+ - ext/io/event/fiber.c
62
+ - ext/io/event/fiber.h
59
63
  - ext/io/event/interrupt.c
60
64
  - ext/io/event/interrupt.h
61
65
  - ext/io/event/list.h
62
- - ext/io/event/profile.c
63
- - ext/io/event/profile.h
66
+ - ext/io/event/profiler.c
67
+ - ext/io/event/profiler.h
64
68
  - ext/io/event/selector/epoll.c
65
69
  - ext/io/event/selector/epoll.h
66
70
  - ext/io/event/selector/kqueue.c
@@ -75,7 +79,9 @@ files:
75
79
  - lib/io/event.rb
76
80
  - lib/io/event/debug/selector.rb
77
81
  - lib/io/event/interrupt.rb
82
+ - lib/io/event/native.rb
78
83
  - lib/io/event/priority_heap.rb
84
+ - lib/io/event/profiler.rb
79
85
  - lib/io/event/selector.rb
80
86
  - lib/io/event/selector/nonblock.rb
81
87
  - lib/io/event/selector/select.rb
metadata.gz.sig CHANGED
Binary file