io-event 1.7.5 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab271a1de3eb0f5d21b0b7c92cafcc226e5ca218e7b87ebaa80f93a54fd7b945
4
- data.tar.gz: fa6227d0b4218b277903fb1c6f1889e51bcfb7e8d8f83086edc30a336a065eff
3
+ metadata.gz: ac3ad1ac294041242bb2f44d9c34bf6871f36b7ae01faaf49b83989b75058e84
4
+ data.tar.gz: e622ec2203010b41c260a763c97cac8025bf86d1d5b04b0ff916f3cf7d7eb467
5
5
  SHA512:
6
- metadata.gz: d5afc83ef6364791d86b14bdf130d68fda3a7a1c720349dce6bb2140a2ee9f69015e542dbf5c724a0daa338e5f13c87f1dee566e8a36a1b0847ce5e86e1fa92b
7
- data.tar.gz: 7d09237e5141123bf251fe686e25fca7d8391a7c25fe331e8882d7e43bf87ff9c5e89bc5c5f90d0731705b923eade10c4667344ad06d04a08544bb9a944baa28
6
+ metadata.gz: dd0ba1be23b1c113af78c07d7d8aab84759d984ba1a0569cdfe31c427e34de7d97a8a7fc5598dd4cfe0ec650709c24bf764c1d5c385b8de34cf70157195dc4e7
7
+ data.tar.gz: 91936ec8d38c517cfdd6a22a72199067f49933811522eb71e4d23df49046725c1708f23481fb5f9430852143093594793f3635fd7a58624e539d97555eafb18a
checksums.yaml.gz.sig CHANGED
@@ -1 +1,2 @@
1
- g�󉨤xIꩅ,�D~���sAR�XxI��̳׿8k�(.����z��;���}��5%��d3x����=�&�5���Po�Ծ�7��zGKZ��Zl�%K������sB��4d ��[3/��2�C�\�z�����=i�c$��#
1
+ ug���YՐ�2�����fi:�I\b��LAua�:;�����n]���ܥ���y^�����}}1���Jget=܋�󠵙ES qy��l�S�2`��‘��.�H=��_���ەolZT��c��<�©
2
+ ��fc�$��=BL}f�Ի����O���
data/ext/extconf.rb CHANGED
@@ -22,7 +22,7 @@ if ENV.key?("RUBY_DEBUG")
22
22
  $CFLAGS << " -DRUBY_DEBUG -O0"
23
23
  end
24
24
 
25
- $srcs = ["io/event/event.c", "io/event/selector/selector.c"]
25
+ $srcs = ["io/event/event.c", "io/event/selector/selector.c", "io/event/time.c", "io/event/profile.c"]
26
26
  $VPATH << "$(srcdir)/io/event"
27
27
  $VPATH << "$(srcdir)/io/event/selector"
28
28
 
@@ -28,7 +28,7 @@ struct IO_Event_Array {
28
28
  void (*element_free)(void*);
29
29
  };
30
30
 
31
- inline static int IO_Event_Array_allocate(struct IO_Event_Array *array, size_t count, size_t element_size)
31
+ inline static int IO_Event_Array_initialize(struct IO_Event_Array *array, size_t count, size_t element_size)
32
32
  {
33
33
  array->limit = 0;
34
34
  array->element_size = element_size;
@@ -153,6 +153,12 @@ inline static void* IO_Event_Array_lookup(struct IO_Event_Array *array, size_t i
153
153
  return *element;
154
154
  }
155
155
 
156
+ inline static void* IO_Event_Array_last(struct IO_Event_Array *array)
157
+ {
158
+ if (array->limit == 0) return NULL;
159
+ else return array->base[array->limit - 1];
160
+ }
161
+
156
162
  // Push a new element onto the end of the array.
157
163
  inline static void* IO_Event_Array_push(struct IO_Event_Array *array)
158
164
  {
data/ext/io/event/event.c CHANGED
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #include "event.h"
22
5
  #include "selector/selector.h"
data/ext/io/event/event.h CHANGED
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #pragma once
22
5
 
@@ -1,24 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
20
-
21
- // static const int DEBUG = 0;
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
22
3
 
23
4
  #include "interrupt.h"
24
5
  #include <unistd.h>
@@ -95,9 +76,11 @@ void IO_Event_Interrupt_signal(struct IO_Event_Interrupt *interrupt)
95
76
  ssize_t result = write(interrupt->descriptor[1], ".", 1);
96
77
 
97
78
  if (result == -1) {
98
- if (errno == EAGAIN || errno == EWOULDBLOCK) return;
99
-
100
- rb_sys_fail("IO_Event_Interrupt_signal:write");
79
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
80
+ // If we can't write to the pipe, it means the other end is full. In that case, we can be sure that the other end has already been woken up or is about to be woken up.
81
+ } else {
82
+ rb_sys_fail("IO_Event_Interrupt_signal:write");
83
+ }
101
84
  }
102
85
  }
103
86
 
@@ -107,9 +90,11 @@ void IO_Event_Interrupt_clear(struct IO_Event_Interrupt *interrupt)
107
90
  ssize_t result = read(interrupt->descriptor[0], buffer, sizeof(buffer));
108
91
 
109
92
  if (result == -1) {
110
- if (errno == EAGAIN || errno == EWOULDBLOCK) return;
111
-
112
- rb_sys_fail("IO_Event_Interrupt_clear:read");
93
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
94
+ // If we can't read from the pipe, it means the other end is empty. In that case, we can be sure that the other end is already clear.
95
+ } else {
96
+ rb_sys_fail("IO_Event_Interrupt_clear:read");
97
+ }
113
98
  }
114
99
  }
115
100
  #endif
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #pragma once
22
5
 
@@ -1,5 +1,5 @@
1
1
  // Released under the MIT License.
2
- // Copyright, 2023, by Samuel Williams.
2
+ // Copyright, 2023-2025, by Samuel Williams.
3
3
 
4
4
  #include <ruby.h>
5
5
  #include <stdio.h>
@@ -0,0 +1,154 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #include "profile.h"
5
+ #include "time.h"
6
+
7
+ #include <ruby/debug.h>
8
+
9
+ #include <stdio.h>
10
+
11
+ void IO_Event_Profile_Event_initialize(struct IO_Event_Profile_Event *event) {
12
+ event->time.tv_sec = 0;
13
+ event->time.tv_nsec = 0;
14
+ event->nesting = 0;
15
+
16
+ event->event_flag = 0;
17
+ event->id = 0;
18
+
19
+ event->path = NULL;
20
+ event->line = 0;
21
+ }
22
+
23
+ void IO_Event_Profile_Event_free(struct IO_Event_Profile_Event *event) {
24
+ if (event->path) {
25
+ free((void*)event->path);
26
+ }
27
+ }
28
+
29
+ static const char *event_flag_name(rb_event_flag_t event_flag) {
30
+ switch (event_flag) {
31
+ case RUBY_EVENT_LINE:
32
+ return "line";
33
+ case RUBY_EVENT_CALL:
34
+ case RUBY_EVENT_C_CALL:
35
+ return "call";
36
+ case RUBY_EVENT_RETURN:
37
+ case RUBY_EVENT_C_RETURN:
38
+ return "return";
39
+ default:
40
+ return "unknown";
41
+ }
42
+ }
43
+
44
+ int event_flag_call_p(rb_event_flag_t event_flags) {
45
+ return event_flags & (RUBY_EVENT_CALL | RUBY_EVENT_C_CALL);
46
+ }
47
+
48
+ int event_flag_return_p(rb_event_flag_t event_flags) {
49
+ return event_flags & (RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN);
50
+ }
51
+
52
+ static void profile_event_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
53
+ struct IO_Event_Profile *profile = (struct IO_Event_Profile*)data;
54
+ struct IO_Event_Profile_Event *event = IO_Event_Array_push(&profile->events);
55
+
56
+ IO_Event_Time_current(&event->time);
57
+
58
+ event->event_flag = event_flag;
59
+
60
+ if (event_flag_call_p(event_flag)) {
61
+ event->parent = profile->current;
62
+ profile->current = event;
63
+
64
+ event->nesting = profile->nesting;
65
+ profile->nesting += 1;
66
+
67
+ if (id) {
68
+ event->id = id;
69
+ event->klass = klass;
70
+ } else {
71
+ rb_frame_method_id_and_class(&event->id, &event->klass);
72
+ }
73
+
74
+ const char *path = rb_sourcefile();
75
+ if (path) {
76
+ event->path = strdup(path);
77
+ }
78
+ event->line = rb_sourceline();
79
+ } else if (event_flag_return_p(event_flag)) {
80
+ // Set up the call/return pair:
81
+ profile->current->pair = event;
82
+ event->pair = profile->current;
83
+
84
+ profile->current = profile->current->parent;
85
+ event->parent = profile->current;
86
+
87
+ profile->nesting -= 1;
88
+ event->nesting = profile->nesting;
89
+ }
90
+ }
91
+
92
+ void IO_Event_Profile_initialize(struct IO_Event_Profile *profile, VALUE fiber) {
93
+ profile->fiber = fiber;
94
+
95
+ profile->events.element_initialize = (void (*)(void*))IO_Event_Profile_Event_initialize;
96
+ profile->events.element_free = (void (*)(void*))IO_Event_Profile_Event_free;
97
+
98
+ IO_Event_Array_initialize(&profile->events, 0, sizeof(struct IO_Event_Profile_Event));
99
+ }
100
+
101
+ void IO_Event_Profile_start(struct IO_Event_Profile *profile) {
102
+ IO_Event_Time_current(&profile->start_time);
103
+ profile->nesting = 0;
104
+ profile->current = NULL;
105
+
106
+ // Since fibers are currently limited to a single thread, we use this in the hope that it's a little more efficient:
107
+ VALUE thread = rb_thread_current();
108
+ rb_thread_add_event_hook(thread, profile_event_callback, RUBY_EVENT_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN, (VALUE)profile);
109
+ }
110
+
111
+ void IO_Event_Profile_stop(struct IO_Event_Profile *profile) {
112
+ IO_Event_Time_current(&profile->stop_time);
113
+
114
+ VALUE thread = rb_thread_current();
115
+ rb_thread_remove_event_hook_with_data(thread, profile_event_callback, (VALUE)profile);
116
+ }
117
+
118
+ void IO_Event_Profile_free(struct IO_Event_Profile *profile) {
119
+ IO_Event_Array_free(&profile->events);
120
+ }
121
+
122
+ static const float IO_EVENT_PROFILE_PRINT_MINIMUM_PROPORTION = 0.01;
123
+
124
+ void IO_Event_Profile_print(FILE *restrict stream, struct IO_Event_Profile *profile) {
125
+ struct timespec total_duration = {};
126
+ IO_Event_Time_elapsed(&profile->start_time, &profile->stop_time, &total_duration);
127
+
128
+ size_t skipped = 0;
129
+
130
+ for (size_t i = 0; i < profile->events.limit; i += 1) {
131
+ struct IO_Event_Profile_Event *event = profile->events.base[i];
132
+
133
+ if (event_flag_call_p(event->event_flag)) {
134
+ struct timespec duration = {};
135
+
136
+ if (event->pair) {
137
+ IO_Event_Time_elapsed(&event->time, &event->pair->time, &duration);
138
+
139
+ // Skip events that are too short to be meaningful:
140
+ if (IO_Event_Time_proportion(&duration, &total_duration) < IO_EVENT_PROFILE_PRINT_MINIMUM_PROPORTION) {
141
+ skipped += 1;
142
+ continue;
143
+ }
144
+ }
145
+
146
+ for (size_t i = 0; i < event->nesting; i += 1) {
147
+ fputc('\t', stream);
148
+ }
149
+
150
+ const char *name = rb_id2name(event->id);
151
+ fprintf(stream, "\t%s:%d in '%s#%s' (" IO_EVENT_TIME_PRINTF_TIMESPEC "s)\n", event->path, event->line, RSTRING_PTR(rb_inspect(event->klass)), name, IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(duration));
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,54 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #pragma once
5
+
6
+ #include <ruby.h>
7
+ #include "array.h"
8
+ #include "time.h"
9
+
10
+ struct IO_Event_Profile_Event {
11
+ struct timespec time;
12
+ size_t nesting;
13
+
14
+ rb_event_flag_t event_flag;
15
+ ID id;
16
+
17
+ VALUE klass;
18
+ const char *path;
19
+ int line;
20
+
21
+ struct IO_Event_Profile_Event *parent;
22
+ struct IO_Event_Profile_Event *pair;
23
+ };
24
+
25
+ struct IO_Event_Profile {
26
+ VALUE fiber;
27
+
28
+ struct timespec start_time;
29
+ struct timespec stop_time;
30
+
31
+ // The depth of the call stack:
32
+ size_t nesting;
33
+
34
+ // The current call frame:
35
+ struct IO_Event_Profile_Event *current;
36
+
37
+ struct IO_Event_Array events;
38
+ };
39
+
40
+ void IO_Event_Profile_initialize(struct IO_Event_Profile *profile, VALUE fiber);
41
+
42
+ void IO_Event_Profile_start(struct IO_Event_Profile *profile);
43
+ void IO_Event_Profile_stop(struct IO_Event_Profile *profile);
44
+
45
+ void IO_Event_Profile_free(struct IO_Event_Profile *profile);
46
+ void IO_Event_Profile_print(FILE *restrict stream, struct IO_Event_Profile *profile);
47
+
48
+ static inline float IO_Event_Profile_duration(struct IO_Event_Profile *profile) {
49
+ struct timespec duration;
50
+
51
+ IO_Event_Time_elapsed(&profile->start_time, &profile->stop_time, &duration);
52
+
53
+ return IO_Event_Time_duration(&duration);
54
+ }
@@ -1,27 +1,10 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #include "epoll.h"
22
5
  #include "selector.h"
23
- #include "list.h"
24
- #include "array.h"
6
+ #include "../list.h"
7
+ #include "../array.h"
25
8
 
26
9
  #include <sys/epoll.h>
27
10
  #include <time.h>
@@ -337,9 +320,9 @@ VALUE IO_Event_Selector_EPoll_allocate(VALUE self) {
337
320
 
338
321
  selector->descriptors.element_initialize = IO_Event_Selector_EPoll_Descriptor_initialize;
339
322
  selector->descriptors.element_free = IO_Event_Selector_EPoll_Descriptor_free;
340
- int result = IO_Event_Array_allocate(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_EPoll_Descriptor));
323
+ int result = IO_Event_Array_initialize(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_EPoll_Descriptor));
341
324
  if (result < 0) {
342
- rb_sys_fail("IO_Event_Selector_EPoll_allocate:IO_Event_Array_allocate");
325
+ rb_sys_fail("IO_Event_Selector_EPoll_allocate:IO_Event_Array_initialize");
343
326
  }
344
327
 
345
328
  return instance;
@@ -435,7 +418,7 @@ VALUE IO_Event_Selector_EPoll_push(VALUE self, VALUE fiber)
435
418
  struct IO_Event_Selector_EPoll *selector = NULL;
436
419
  TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, selector);
437
420
 
438
- IO_Event_Selector_queue_push(&selector->backend, fiber);
421
+ IO_Event_Selector_ready_push(&selector->backend, fiber);
439
422
 
440
423
  return Qnil;
441
424
  }
@@ -584,7 +567,7 @@ VALUE IO_Event_Selector_EPoll_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE e
584
567
 
585
568
  if (result == -1) {
586
569
  if (errno == EPERM) {
587
- IO_Event_Selector_queue_push(&selector->backend, fiber);
570
+ IO_Event_Selector_ready_push(&selector->backend, fiber);
588
571
  IO_Event_Selector_yield(&selector->backend);
589
572
  return events;
590
573
  }
@@ -942,7 +925,7 @@ int IO_Event_Selector_EPoll_handle(struct IO_Event_Selector_EPoll *selector, con
942
925
 
943
926
  // Resume the fiber:
944
927
  waiting->ready = matching_events;
945
- IO_Event_Selector_fiber_transfer(waiting->fiber, 0, NULL);
928
+ IO_Event_Selector_fiber_transfer_user(waiting->fiber, 0, NULL);
946
929
 
947
930
  node = saved->tail;
948
931
  IO_Event_List_pop(saved);
@@ -994,7 +977,7 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
994
977
  selector->idle_duration.tv_sec = 0;
995
978
  selector->idle_duration.tv_nsec = 0;
996
979
 
997
- int ready = IO_Event_Selector_queue_flush(&selector->backend);
980
+ int ready = IO_Event_Selector_ready_flush(&selector->backend);
998
981
 
999
982
  struct select_arguments arguments = {
1000
983
  .selector = selector,
@@ -1020,14 +1003,14 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
1020
1003
 
1021
1004
  if (!timeout_nonblocking(arguments.timeout)) {
1022
1005
  struct timespec start_time;
1023
- IO_Event_Selector_current_time(&start_time);
1006
+ IO_Event_Time_current(&start_time);
1024
1007
 
1025
1008
  // Wait for events to occur:
1026
1009
  select_internal_without_gvl(&arguments);
1027
1010
 
1028
1011
  struct timespec end_time;
1029
- IO_Event_Selector_current_time(&end_time);
1030
- IO_Event_Selector_elapsed_time(&start_time, &end_time, &selector->idle_duration);
1012
+ IO_Event_Time_current(&end_time);
1013
+ IO_Event_Time_elapsed(&start_time, &end_time, &selector->idle_duration);
1031
1014
  }
1032
1015
  }
1033
1016
 
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #pragma once
22
5
 
@@ -1,27 +1,10 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #include "kqueue.h"
22
5
  #include "selector.h"
23
- #include "list.h"
24
- #include "array.h"
6
+ #include "../list.h"
7
+ #include "../array.h"
25
8
 
26
9
  #include <sys/event.h>
27
10
  #include <sys/ioctl.h>
@@ -312,9 +295,9 @@ VALUE IO_Event_Selector_KQueue_allocate(VALUE self) {
312
295
  selector->descriptors.element_initialize = IO_Event_Selector_KQueue_Descriptor_initialize;
313
296
  selector->descriptors.element_free = IO_Event_Selector_KQueue_Descriptor_free;
314
297
 
315
- int result = IO_Event_Array_allocate(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_KQueue_Descriptor));
298
+ int result = IO_Event_Array_initialize(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_KQueue_Descriptor));
316
299
  if (result < 0) {
317
- rb_sys_fail("IO_Event_Selector_KQueue_allocate:IO_Event_Array_allocate");
300
+ rb_sys_fail("IO_Event_Selector_KQueue_allocate:IO_Event_Array_initialize");
318
301
  }
319
302
 
320
303
  return instance;
@@ -422,7 +405,7 @@ VALUE IO_Event_Selector_KQueue_push(VALUE self, VALUE fiber)
422
405
  struct IO_Event_Selector_KQueue *selector = NULL;
423
406
  TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
424
407
 
425
- IO_Event_Selector_queue_push(&selector->backend, fiber);
408
+ IO_Event_Selector_ready_push(&selector->backend, fiber);
426
409
 
427
410
  return Qnil;
428
411
  }
@@ -914,7 +897,7 @@ int IO_Event_Selector_KQueue_handle(struct IO_Event_Selector_KQueue *selector, u
914
897
  IO_Event_List_append(node, saved);
915
898
 
916
899
  waiting->ready = matching_events;
917
- IO_Event_Selector_fiber_transfer(waiting->fiber, 0, NULL);
900
+ IO_Event_Selector_fiber_transfer_user(waiting->fiber, 0, NULL);
918
901
 
919
902
  node = saved->tail;
920
903
  IO_Event_List_pop(saved);
@@ -971,7 +954,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
971
954
  selector->idle_duration.tv_sec = 0;
972
955
  selector->idle_duration.tv_nsec = 0;
973
956
 
974
- int ready = IO_Event_Selector_queue_flush(&selector->backend);
957
+ int ready = IO_Event_Selector_ready_flush(&selector->backend);
975
958
 
976
959
  struct select_arguments arguments = {
977
960
  .selector = selector,
@@ -1008,14 +991,14 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
1008
991
  arguments.count = KQUEUE_MAX_EVENTS;
1009
992
 
1010
993
  struct timespec start_time;
1011
- IO_Event_Selector_current_time(&start_time);
994
+ IO_Event_Time_current(&start_time);
1012
995
 
1013
996
  if (DEBUG) fprintf(stderr, "IO_Event_Selector_KQueue_select timeout=" IO_EVENT_PRINTF_TIMESPEC "\n", IO_EVENT_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage));
1014
997
  select_internal_without_gvl(&arguments);
1015
998
 
1016
999
  struct timespec end_time;
1017
- IO_Event_Selector_current_time(&end_time);
1018
- IO_Event_Selector_elapsed_time(&start_time, &end_time, &selector->idle_duration);
1000
+ IO_Event_Time_current(&end_time);
1001
+ IO_Event_Time_elapsed(&start_time, &end_time, &selector->idle_duration);
1019
1002
  }
1020
1003
  }
1021
1004
 
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #pragma once
22
5
 
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #include <ruby.h>
22
5
 
@@ -1,30 +1,18 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #include "selector.h"
5
+ #include "../profile.h"
6
+
22
7
  #include <fcntl.h>
8
+ #include <stdlib.h>
23
9
 
24
10
  static const int DEBUG = 0;
25
11
 
26
12
  static ID id_transfer, id_alive_p;
27
13
 
14
+ static float IO_Event_Selector_stall_log_threshold = 0;
15
+
28
16
  VALUE IO_Event_Selector_fiber_transfer(VALUE fiber, int argc, VALUE *argv) {
29
17
  // TODO Consider introducing something like `rb_fiber_scheduler_transfer(...)`.
30
18
  #ifdef HAVE__RB_FIBER_TRANSFER
@@ -43,6 +31,31 @@ VALUE IO_Event_Selector_fiber_transfer(VALUE fiber, int argc, VALUE *argv) {
43
31
  return Qnil;
44
32
  }
45
33
 
34
+ VALUE IO_Event_Selector_fiber_transfer_user(VALUE fiber, int argc, VALUE *argv) {
35
+ // Bypass if the threshold is not set:
36
+ if (IO_Event_Selector_stall_log_threshold == 0) {
37
+ return IO_Event_Selector_fiber_transfer(fiber, argc, argv);
38
+ }
39
+
40
+ struct IO_Event_Profile profile;
41
+ IO_Event_Profile_initialize(&profile, fiber);
42
+ IO_Event_Profile_start(&profile);
43
+
44
+ // Transfer control to the fiber:
45
+ VALUE result = IO_Event_Selector_fiber_transfer(fiber, argc, argv);
46
+
47
+ IO_Event_Profile_stop(&profile);
48
+
49
+ float duration = IO_Event_Profile_duration(&profile);
50
+
51
+ if (duration > IO_Event_Selector_stall_log_threshold) {
52
+ fprintf(stderr, "Fiber stalled for %.3f seconds\n", duration);
53
+ IO_Event_Profile_print(stderr, &profile);
54
+ }
55
+
56
+ return result;
57
+ }
58
+
46
59
  #ifndef HAVE__RB_FIBER_RAISE
47
60
  static ID id_raise;
48
61
 
@@ -156,8 +169,25 @@ void Init_IO_Event_Selector(VALUE IO_Event_Selector) {
156
169
  rb_Process_Status = rb_const_get_at(rb_mProcess, rb_intern("Status"));
157
170
  rb_gc_register_mark_object(rb_Process_Status);
158
171
  #endif
159
-
172
+
160
173
  rb_define_singleton_method(IO_Event_Selector, "nonblock", IO_Event_Selector_nonblock, 1);
174
+
175
+ // Extract the stall log threshold if specified:
176
+
177
+ char *stall_log_threshold = getenv("IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD");
178
+ // Can be true, false or a floating point time in seconds:
179
+
180
+ if (stall_log_threshold) {
181
+ if (strcmp(stall_log_threshold, "true") == 0) {
182
+ IO_Event_Selector_stall_log_threshold = 0.001;
183
+ } else if (strcmp(stall_log_threshold, "false") == 0) {
184
+ IO_Event_Selector_stall_log_threshold = 0;
185
+ } else {
186
+ IO_Event_Selector_stall_log_threshold = strtof(stall_log_threshold, NULL);
187
+ }
188
+
189
+ if (DEBUG) fprintf(stderr, "IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD = %.3f\n", IO_Event_Selector_stall_log_threshold);
190
+ }
161
191
  }
162
192
 
163
193
  struct wait_and_transfer_arguments {
@@ -211,7 +241,7 @@ static VALUE wait_and_transfer(VALUE _arguments) {
211
241
  int argc = arguments->argc - 1;
212
242
  VALUE *argv = arguments->argv + 1;
213
243
 
214
- return IO_Event_Selector_fiber_transfer(fiber, argc, argv);
244
+ return IO_Event_Selector_fiber_transfer_user(fiber, argc, argv);
215
245
  }
216
246
 
217
247
  static VALUE wait_and_transfer_ensure(VALUE _arguments) {
@@ -282,7 +312,7 @@ VALUE IO_Event_Selector_raise(struct IO_Event_Selector *backend, int argc, VALUE
282
312
  return rb_ensure(wait_and_raise, (VALUE)&arguments, wait_and_transfer_ensure, (VALUE)&arguments);
283
313
  }
284
314
 
285
- void IO_Event_Selector_queue_push(struct IO_Event_Selector *backend, VALUE fiber)
315
+ void IO_Event_Selector_ready_push(struct IO_Event_Selector *backend, VALUE fiber)
286
316
  {
287
317
  struct IO_Event_Selector_Queue *waiting = malloc(sizeof(struct IO_Event_Selector_Queue));
288
318
  assert(waiting);
@@ -297,26 +327,26 @@ void IO_Event_Selector_queue_push(struct IO_Event_Selector *backend, VALUE fiber
297
327
  }
298
328
 
299
329
  static inline
300
- void IO_Event_Selector_queue_pop(struct IO_Event_Selector *backend, struct IO_Event_Selector_Queue *ready)
330
+ void IO_Event_Selector_ready_pop(struct IO_Event_Selector *backend, struct IO_Event_Selector_Queue *ready)
301
331
  {
302
- if (DEBUG) fprintf(stderr, "IO_Event_Selector_queue_pop -> %p\n", (void*)ready->fiber);
332
+ if (DEBUG) fprintf(stderr, "IO_Event_Selector_ready_pop -> %p\n", (void*)ready->fiber);
303
333
 
304
- if (ready->flags & IO_EVENT_SELECTOR_QUEUE_FIBER) {
305
- IO_Event_Selector_fiber_transfer(ready->fiber, 0, NULL);
306
- } else if (ready->flags & IO_EVENT_SELECTOR_QUEUE_INTERNAL) {
307
- VALUE fiber = ready->fiber;
334
+ VALUE fiber = ready->fiber;
335
+
336
+ if (ready->flags & IO_EVENT_SELECTOR_QUEUE_INTERNAL) {
337
+ // This means that the fiber was added to the ready queue by the selector itself, and we need to transfer control to it, but before we do that, we need to remove it from the queue, as there is no expectation that returning from `transfer` will remove it.
308
338
  queue_pop(backend, ready);
309
339
  free(ready);
310
-
311
- if (RTEST(rb_funcall(fiber, id_alive_p, 0))) {
312
- rb_funcall(fiber, id_transfer, 0);
313
- }
340
+ } else if (ready->flags & IO_EVENT_SELECTOR_QUEUE_FIBER) {
341
+ // This means the fiber added itself to the ready queue, and we need to transfer control back to it. Transferring control back to the fiber will call `queue_pop` and remove it from the queue.
314
342
  } else {
315
343
  rb_raise(rb_eRuntimeError, "Unknown queue type!");
316
344
  }
345
+
346
+ IO_Event_Selector_fiber_transfer_user(fiber, 0, NULL);
317
347
  }
318
348
 
319
- int IO_Event_Selector_queue_flush(struct IO_Event_Selector *backend)
349
+ int IO_Event_Selector_ready_flush(struct IO_Event_Selector *backend)
320
350
  {
321
351
  int count = 0;
322
352
 
@@ -324,7 +354,7 @@ int IO_Event_Selector_queue_flush(struct IO_Event_Selector *backend)
324
354
 
325
355
  // Get the current tail and head of the queue:
326
356
  struct IO_Event_Selector_Queue *waiting = backend->waiting;
327
- if (DEBUG) fprintf(stderr, "IO_Event_Selector_queue_flush waiting = %p\n", waiting);
357
+ if (DEBUG) fprintf(stderr, "IO_Event_Selector_ready_flush waiting = %p\n", waiting);
328
358
 
329
359
  // Process from head to tail in order:
330
360
  // During this, more items may be appended to tail.
@@ -333,25 +363,10 @@ int IO_Event_Selector_queue_flush(struct IO_Event_Selector *backend)
333
363
  struct IO_Event_Selector_Queue *ready = backend->ready;
334
364
 
335
365
  count += 1;
336
- IO_Event_Selector_queue_pop(backend, ready);
366
+ IO_Event_Selector_ready_pop(backend, ready);
337
367
 
338
368
  if (ready == waiting) break;
339
369
  }
340
370
 
341
371
  return count;
342
372
  }
343
-
344
- void IO_Event_Selector_elapsed_time(struct timespec* start, struct timespec* stop, struct timespec *duration)
345
- {
346
- if ((stop->tv_nsec - start->tv_nsec) < 0) {
347
- duration->tv_sec = stop->tv_sec - start->tv_sec - 1;
348
- duration->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
349
- } else {
350
- duration->tv_sec = stop->tv_sec - start->tv_sec;
351
- duration->tv_nsec = stop->tv_nsec - start->tv_nsec;
352
- }
353
- }
354
-
355
- void IO_Event_Selector_current_time(struct timespec *time) {
356
- clock_gettime(CLOCK_MONOTONIC, time);
357
- }
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #pragma once
22
5
 
@@ -24,6 +7,8 @@
24
7
  #include <ruby/thread.h>
25
8
  #include <ruby/io.h>
26
9
 
10
+ #include "../time.h"
11
+
27
12
  #ifdef HAVE_RUBY_IO_BUFFER_H
28
13
  #include <ruby/io/buffer.h>
29
14
  #include <ruby/fiber/scheduler.h>
@@ -33,8 +18,6 @@
33
18
  #define RUBY_FIBER_SCHEDULER_VERSION 1
34
19
  #endif
35
20
 
36
- #include <time.h>
37
-
38
21
  #ifdef HAVE_SYS_WAIT_H
39
22
  #include <sys/wait.h>
40
23
  #endif
@@ -58,6 +41,9 @@ static inline int IO_Event_try_again(int error) {
58
41
 
59
42
  VALUE IO_Event_Selector_fiber_transfer(VALUE fiber, int argc, VALUE *argv);
60
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
+
61
47
  #ifdef HAVE__RB_FIBER_RAISE
62
48
  #define IO_Event_Selector_fiber_raise(fiber, argc, argv) rb_fiber_raise(fiber, argc, argv)
63
49
  #else
@@ -146,11 +132,8 @@ VALUE IO_Event_Selector_yield(struct IO_Event_Selector *backend)
146
132
  return IO_Event_Selector_resume(backend, 1, &backend->loop);
147
133
  }
148
134
 
149
- void IO_Event_Selector_queue_push(struct IO_Event_Selector *backend, VALUE fiber);
150
- int IO_Event_Selector_queue_flush(struct IO_Event_Selector *backend);
151
-
152
- void IO_Event_Selector_elapsed_time(struct timespec* start, struct timespec* stop, struct timespec *duration);
153
- void IO_Event_Selector_current_time(struct timespec *time);
135
+ // Append to the ready queue.
136
+ void IO_Event_Selector_ready_push(struct IO_Event_Selector *backend, VALUE fiber);
154
137
 
155
- #define IO_EVENT_PRINTF_TIMESPEC "%lld.%.9ld"
156
- #define IO_EVENT_PRINTF_TIMESPEC_ARGUMENTS(ts) (long long)((ts).tv_sec), (ts).tv_nsec
138
+ // Flush the ready queue by transferring control one at a time.
139
+ int IO_Event_Selector_ready_flush(struct IO_Event_Selector *backend);
@@ -1,27 +1,10 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #include "uring.h"
22
5
  #include "selector.h"
23
- #include "list.h"
24
- #include "array.h"
6
+ #include "../list.h"
7
+ #include "../array.h"
25
8
 
26
9
  #include <liburing.h>
27
10
  #include <poll.h>
@@ -238,9 +221,9 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) {
238
221
 
239
222
  selector->completions.element_initialize = IO_Event_Selector_URing_Completion_initialize;
240
223
  selector->completions.element_free = IO_Event_Selector_URing_Completion_free;
241
- int result = IO_Event_Array_allocate(&selector->completions, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_URing_Completion));
224
+ int result = IO_Event_Array_initialize(&selector->completions, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_URing_Completion));
242
225
  if (result < 0) {
243
- rb_sys_fail("IO_Event_Selector_URing_allocate:IO_Event_Array_allocate");
226
+ rb_sys_fail("IO_Event_Selector_URing_allocate:IO_Event_Array_initialize");
244
227
  }
245
228
 
246
229
  return instance;
@@ -318,7 +301,7 @@ VALUE IO_Event_Selector_URing_push(VALUE self, VALUE fiber)
318
301
  struct IO_Event_Selector_URing *selector = NULL;
319
302
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
320
303
 
321
- IO_Event_Selector_queue_push(&selector->backend, fiber);
304
+ IO_Event_Selector_ready_push(&selector->backend, fiber);
322
305
 
323
306
  return Qnil;
324
307
  }
@@ -1103,7 +1086,7 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1103
1086
  if (waiting && waiting->fiber) {
1104
1087
  assert(waiting->result != -ECANCELED);
1105
1088
 
1106
- IO_Event_Selector_fiber_transfer(waiting->fiber, 0, NULL);
1089
+ IO_Event_Selector_fiber_transfer_user(waiting->fiber, 0, NULL);
1107
1090
  }
1108
1091
  }
1109
1092
 
@@ -1122,7 +1105,7 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
1122
1105
  // Flush any pending events:
1123
1106
  io_uring_submit_flush(selector);
1124
1107
 
1125
- int ready = IO_Event_Selector_queue_flush(&selector->backend);
1108
+ int ready = IO_Event_Selector_ready_flush(&selector->backend);
1126
1109
 
1127
1110
  int result = select_process_completions(selector);
1128
1111
 
@@ -1142,14 +1125,14 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
1142
1125
 
1143
1126
  if (!selector->backend.ready && !timeout_nonblocking(arguments.timeout)) {
1144
1127
  struct timespec start_time;
1145
- IO_Event_Selector_current_time(&start_time);
1128
+ IO_Event_Time_current(&start_time);
1146
1129
 
1147
1130
  // This is a blocking operation, we wait for events:
1148
1131
  result = select_internal_without_gvl(&arguments);
1149
1132
 
1150
1133
  struct timespec end_time;
1151
- IO_Event_Selector_current_time(&end_time);
1152
- IO_Event_Selector_elapsed_time(&start_time, &end_time, &selector->idle_duration);
1134
+ IO_Event_Time_current(&end_time);
1135
+ IO_Event_Time_elapsed(&start_time, &end_time, &selector->idle_duration);
1153
1136
 
1154
1137
  // After waiting/flushing the SQ, check if there are any completions:
1155
1138
  if (result > 0) {
@@ -1,22 +1,5 @@
1
- // Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in
11
- // all copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- // THE SOFTWARE.
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
20
3
 
21
4
  #pragma once
22
5
 
@@ -0,0 +1,28 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #include "time.h"
5
+
6
+ void IO_Event_Time_elapsed(struct timespec* start, struct timespec* stop, struct timespec *duration)
7
+ {
8
+ if ((stop->tv_nsec - start->tv_nsec) < 0) {
9
+ duration->tv_sec = stop->tv_sec - start->tv_sec - 1;
10
+ duration->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
11
+ } else {
12
+ duration->tv_sec = stop->tv_sec - start->tv_sec;
13
+ duration->tv_nsec = stop->tv_nsec - start->tv_nsec;
14
+ }
15
+ }
16
+
17
+ float IO_Event_Time_duration(struct timespec *duration)
18
+ {
19
+ return duration->tv_sec + duration->tv_nsec / 1000000000.0;
20
+ }
21
+
22
+ void IO_Event_Time_current(struct timespec *time) {
23
+ clock_gettime(CLOCK_MONOTONIC, time);
24
+ }
25
+
26
+ float IO_Event_Time_proportion(struct timespec *duration, struct timespec *total_duration) {
27
+ return IO_Event_Time_duration(duration) / IO_Event_Time_duration(total_duration);
28
+ }
@@ -0,0 +1,16 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2025, by Samuel Williams.
3
+
4
+ #pragma once
5
+
6
+ #include <ruby.h>
7
+ #include <time.h>
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);
11
+ void IO_Event_Time_current(struct timespec *time);
12
+
13
+ float IO_Event_Time_proportion(struct timespec *duration, struct timespec *total_duration);
14
+
15
+ #define IO_EVENT_TIME_PRINTF_TIMESPEC "%.3g"
16
+ #define IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(ts) ((double)(ts).tv_sec + (ts).tv_nsec / 1e9)
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.7.5"
10
+ VERSION = "1.8.0"
11
11
  end
12
12
  end
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.8.0
22
+
23
+ - [Detecing fibers that are stalling the event loop.](https://socketry.github.io/io-event/releases/index#detecing-fibers-that-are-stalling-the-event-loop.)
24
+
21
25
  ### v1.7.5
22
26
 
23
27
  - Fix `process_wait` race condition on EPoll that could cause a hang.
data/releases.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Releases
2
2
 
3
+ ## v1.8.0
4
+
5
+ ### Detecing fibers that are stalling the event loop.
6
+
7
+ A new (experimental) feature for detecting fiber stalls has been added. This feature is disabled by default and can be enabled by setting the `IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD` to `true` or a floating point number representing the threshold in seconds.
8
+
9
+ When enabled, the event loop will measure and profile user code when resuming a fiber. If the fiber takes too long to return back to the event loop, the event loop will log a warning message with a profile of the fiber's execution.
10
+
11
+ > cat test.rb
12
+ #!/usr/bin/env ruby
13
+
14
+ require_relative "lib/async"
15
+
16
+ Async do
17
+ Fiber.blocking do
18
+ sleep 1
19
+ end
20
+ end
21
+
22
+ > IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD=true bundle exec ./test.rb
23
+ Fiber stalled for 1.003 seconds
24
+ /home/samuel/Developer/socketry/async/test.rb:6 in '#<Class:Fiber>#blocking' (1s)
25
+ /home/samuel/Developer/socketry/async/test.rb:7 in 'Kernel#sleep' (1s)
26
+
27
+ There is a performance overhead to this feature, so it is recommended to only enable it when debugging performance issues.
28
+
3
29
  ## v1.7.5
4
30
 
5
31
  - Fix `process_wait` race condition on EPoll that could cause a hang.
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.7.5
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -13,7 +13,6 @@ authors:
13
13
  - Anthony Ross
14
14
  - Delton Ding
15
15
  - Pavel Rosický
16
- autorequire:
17
16
  bindir: bin
18
17
  cert_chain:
19
18
  - |
@@ -45,10 +44,8 @@ cert_chain:
45
44
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
46
45
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
47
46
  -----END CERTIFICATE-----
48
- date: 2024-12-15 00:00:00.000000000 Z
47
+ date: 2025-02-05 00:00:00.000000000 Z
49
48
  dependencies: []
50
- description:
51
- email:
52
49
  executables: []
53
50
  extensions:
54
51
  - ext/extconf.rb
@@ -56,21 +53,25 @@ extra_rdoc_files: []
56
53
  files:
57
54
  - design.md
58
55
  - ext/extconf.rb
56
+ - ext/io/event/array.h
59
57
  - ext/io/event/event.c
60
58
  - ext/io/event/event.h
61
59
  - ext/io/event/interrupt.c
62
60
  - ext/io/event/interrupt.h
63
- - ext/io/event/selector/array.h
61
+ - ext/io/event/list.h
62
+ - ext/io/event/profile.c
63
+ - ext/io/event/profile.h
64
64
  - ext/io/event/selector/epoll.c
65
65
  - ext/io/event/selector/epoll.h
66
66
  - ext/io/event/selector/kqueue.c
67
67
  - ext/io/event/selector/kqueue.h
68
- - ext/io/event/selector/list.h
69
68
  - ext/io/event/selector/pidfd.c
70
69
  - ext/io/event/selector/selector.c
71
70
  - ext/io/event/selector/selector.h
72
71
  - ext/io/event/selector/uring.c
73
72
  - ext/io/event/selector/uring.h
73
+ - ext/io/event/time.c
74
+ - ext/io/event/time.h
74
75
  - lib/io/event.rb
75
76
  - lib/io/event/debug/selector.rb
76
77
  - lib/io/event/interrupt.rb
@@ -90,7 +91,6 @@ licenses:
90
91
  metadata:
91
92
  documentation_uri: https://socketry.github.io/io-event/
92
93
  source_code_uri: https://github.com/socketry/io-event.git
93
- post_install_message:
94
94
  rdoc_options: []
95
95
  require_paths:
96
96
  - lib
@@ -105,8 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
105
  - !ruby/object:Gem::Version
106
106
  version: '0'
107
107
  requirements: []
108
- rubygems_version: 3.5.22
109
- signing_key:
108
+ rubygems_version: 3.6.2
110
109
  specification_version: 4
111
110
  summary: An event loop.
112
111
  test_files: []
metadata.gz.sig CHANGED
Binary file