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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/ext/extconf.rb +2 -2
- data/ext/io/event/array.h +16 -0
- data/ext/io/event/event.c +5 -2
- data/ext/io/event/fiber.c +63 -0
- data/ext/io/event/fiber.h +23 -0
- data/ext/io/event/profiler.c +505 -0
- data/ext/io/event/profiler.h +8 -0
- data/ext/io/event/selector/epoll.c +5 -5
- data/ext/io/event/selector/kqueue.c +5 -5
- data/ext/io/event/selector/selector.c +23 -104
- data/ext/io/event/selector/selector.h +38 -19
- data/ext/io/event/selector/uring.c +7 -7
- data/ext/io/event/time.c +10 -3
- data/ext/io/event/time.h +4 -3
- data/lib/io/event/native.rb +11 -0
- data/lib/io/event/profiler.rb +18 -0
- data/lib/io/event/version.rb +2 -2
- data/lib/io/event.rb +2 -8
- data/license.md +1 -1
- data/readme.md +4 -0
- data/releases.md +26 -0
- data.tar.gz.sig +0 -0
- metadata +10 -4
- metadata.gz.sig +0 -0
- data/ext/io/event/profile.c +0 -245
- data/ext/io/event/profile.h +0 -63
@@ -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 = "
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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 = "
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
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 =
|
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
|
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 =
|
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
|
-
|
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
|
-
|
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 = "
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
data/lib/io/event/version.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2021-
|
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.
|
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-
|
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
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.
|
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-
|
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/
|
63
|
-
- ext/io/event/
|
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
|