io-event 1.6.5 → 1.7.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53b1f32affa83ac713eaaf8fba4b9a1c9d2f25421908759dde9b5adec11c498f
4
- data.tar.gz: 6504222eab182d55f2b56ef91c4de9f301f509a69fff17f286c03f0ef0315786
3
+ metadata.gz: ab271a1de3eb0f5d21b0b7c92cafcc226e5ca218e7b87ebaa80f93a54fd7b945
4
+ data.tar.gz: fa6227d0b4218b277903fb1c6f1889e51bcfb7e8d8f83086edc30a336a065eff
5
5
  SHA512:
6
- metadata.gz: ac5f863272fb2a532d1adbe16e2960f78dd6f4ce52891e391f301d40bb28ad74e2775348d55610f1f308f7a69b0b362e93f794b44fe94b4ec6e91331a2e851d5
7
- data.tar.gz: 042a1a3ba4ec1a3e438d823b9d9348eeebc86be2464030744a9a01ff50654aeeb229fb79729083d3f1bdb2feb968dcee8ec320d33de7083196eb1afd887c5ef6
6
+ metadata.gz: d5afc83ef6364791d86b14bdf130d68fda3a7a1c720349dce6bb2140a2ee9f69015e542dbf5c724a0daa338e5f13c87f1dee566e8a36a1b0847ce5e86e1fa92b
7
+ data.tar.gz: 7d09237e5141123bf251fe686e25fca7d8391a7c25fe331e8882d7e43bf87ff9c5e89bc5c5f90d0731705b923eade10c4667344ad06d04a08544bb9a944baa28
checksums.yaml.gz.sig CHANGED
Binary file
data/ext/extconf.rb CHANGED
@@ -2,21 +2,23 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Released under the MIT License.
5
- # Copyright, 2021-2023, by Samuel Williams.
5
+ # Copyright, 2021-2024, by Samuel Williams.
6
6
  # Copyright, 2023, by Math Ieu.
7
7
 
8
8
  return if RUBY_DESCRIPTION =~ /jruby/
9
9
 
10
- require 'mkmf'
10
+ require "mkmf"
11
11
 
12
12
  gem_name = File.basename(__dir__)
13
- extension_name = 'IO_Event'
13
+ extension_name = "IO_Event"
14
14
 
15
15
  # dir_config(extension_name)
16
16
 
17
17
  $CFLAGS << " -Wall -Wno-unknown-pragmas -std=c99"
18
18
 
19
- if ENV.key?('RUBY_DEBUG')
19
+ if ENV.key?("RUBY_DEBUG")
20
+ $stderr.puts "Enabling debug mode..."
21
+
20
22
  $CFLAGS << " -DRUBY_DEBUG -O0"
21
23
  end
22
24
 
@@ -24,24 +26,27 @@ $srcs = ["io/event/event.c", "io/event/selector/selector.c"]
24
26
  $VPATH << "$(srcdir)/io/event"
25
27
  $VPATH << "$(srcdir)/io/event/selector"
26
28
 
27
- have_func('rb_ext_ractor_safe')
28
- have_func('&rb_fiber_transfer')
29
+ have_func("rb_ext_ractor_safe")
30
+ have_func("&rb_fiber_transfer")
29
31
 
30
- if have_library('uring') and have_header('liburing.h')
32
+ if have_library("uring") and have_header("liburing.h")
33
+ # We might want to consider using this in the future:
34
+ # have_func("io_uring_submit_and_wait_timeout", "liburing.h")
35
+
31
36
  $srcs << "io/event/selector/uring.c"
32
37
  end
33
38
 
34
- if have_header('sys/epoll.h')
39
+ if have_header("sys/epoll.h")
35
40
  $srcs << "io/event/selector/epoll.c"
36
41
  end
37
42
 
38
- if have_header('sys/event.h')
43
+ if have_header("sys/event.h")
39
44
  $srcs << "io/event/selector/kqueue.c"
40
45
  end
41
46
 
42
- have_header('sys/wait.h')
47
+ have_header("sys/wait.h")
43
48
 
44
- have_header('sys/eventfd.h')
49
+ have_header("sys/eventfd.h")
45
50
  $srcs << "io/event/interrupt.c"
46
51
 
47
52
  have_func("rb_io_descriptor")
@@ -50,7 +55,15 @@ have_func("rb_fiber_current")
50
55
  have_func("&rb_fiber_raise")
51
56
  have_func("epoll_pwait2")
52
57
 
53
- have_header('ruby/io/buffer.h')
58
+ have_header("ruby/io/buffer.h")
59
+
60
+ if ENV.key?("RUBY_SANITIZE")
61
+ $stderr.puts "Enabling sanitizers..."
62
+
63
+ # Add address and undefined behaviour sanitizers:
64
+ $CFLAGS << " -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer"
65
+ $LDFLAGS << " -fsanitize=address -fsanitize=undefined"
66
+ end
54
67
 
55
68
  create_header
56
69
 
data/ext/io/event/event.c CHANGED
@@ -21,20 +21,14 @@
21
21
  #include "event.h"
22
22
  #include "selector/selector.h"
23
23
 
24
- VALUE IO_Event = Qnil;
25
- VALUE IO_Event_Selector = Qnil;
26
-
27
24
  void Init_IO_Event(void)
28
25
  {
29
26
  #ifdef HAVE_RB_EXT_RACTOR_SAFE
30
27
  rb_ext_ractor_safe(true);
31
28
  #endif
32
29
 
33
- IO_Event = rb_define_module_under(rb_cIO, "Event");
34
- rb_gc_register_mark_object(IO_Event);
35
-
36
- IO_Event_Selector = rb_define_module_under(IO_Event, "Selector");
37
- rb_gc_register_mark_object(IO_Event_Selector);
30
+ VALUE IO_Event = rb_define_module_under(rb_cIO, "Event");
31
+ VALUE IO_Event_Selector = rb_define_module_under(IO_Event, "Selector");
38
32
 
39
33
  Init_IO_Event_Selector(IO_Event_Selector);
40
34
 
@@ -25,6 +25,13 @@
25
25
 
26
26
  #include "selector/selector.h"
27
27
 
28
+ #ifdef HAVE_RUBY_WIN32_H
29
+ #include <ruby/win32.h>
30
+ #if !defined(HAVE_PIPE) && !defined(pipe)
31
+ #define pipe(p) rb_w32_pipe(p)
32
+ #endif
33
+ #endif
34
+
28
35
  #ifdef HAVE_SYS_EVENTFD_H
29
36
  #include <sys/eventfd.h>
30
37
 
@@ -8,6 +8,9 @@
8
8
  #include <errno.h>
9
9
  #include <assert.h>
10
10
 
11
+ static const size_t IO_EVENT_ARRAY_MAXIMUM_COUNT = SIZE_MAX / sizeof(void*);
12
+ static const size_t IO_EVENT_ARRAY_DEFAULT_COUNT = 128;
13
+
11
14
  struct IO_Event_Array {
12
15
  // The array of pointers to elements:
13
16
  void **base;
@@ -25,18 +28,27 @@ struct IO_Event_Array {
25
28
  void (*element_free)(void*);
26
29
  };
27
30
 
28
- inline static void IO_Event_Array_allocate(struct IO_Event_Array *array, size_t count, size_t element_size)
31
+ inline static int IO_Event_Array_allocate(struct IO_Event_Array *array, size_t count, size_t element_size)
29
32
  {
33
+ array->limit = 0;
34
+ array->element_size = element_size;
35
+
30
36
  if (count) {
31
37
  array->base = (void**)calloc(count, sizeof(void*));
38
+
39
+ if (array->base == NULL) {
40
+ return -1;
41
+ }
42
+
32
43
  array->count = count;
44
+
45
+ return 1;
33
46
  } else {
34
47
  array->base = NULL;
35
48
  array->count = 0;
49
+
50
+ return 0;
36
51
  }
37
-
38
- array->limit = 0;
39
- array->element_size = element_size;
40
52
  }
41
53
 
42
54
  inline static size_t IO_Event_Array_memory_size(const struct IO_Event_Array *array)
@@ -47,31 +59,51 @@ inline static size_t IO_Event_Array_memory_size(const struct IO_Event_Array *arr
47
59
 
48
60
  inline static void IO_Event_Array_free(struct IO_Event_Array *array)
49
61
  {
50
- for (size_t i = 0; i < array->limit; i += 1) {
51
- void *element = array->base[i];
52
- if (element) {
53
- array->element_free(element);
54
- free(element);
62
+ if (array->base) {
63
+ void **base = array->base;
64
+ size_t limit = array->limit;
65
+
66
+ array->base = NULL;
67
+ array->count = 0;
68
+ array->limit = 0;
69
+
70
+ for (size_t i = 0; i < limit; i += 1) {
71
+ void *element = base[i];
72
+ if (element) {
73
+ array->element_free(element);
74
+
75
+ free(element);
76
+ }
55
77
  }
78
+
79
+ free(base);
56
80
  }
57
-
58
- if (array->base)
59
- free(array->base);
60
-
61
- array->base = NULL;
62
- array->count = 0;
63
- array->limit = 0;
64
81
  }
65
82
 
66
83
  inline static int IO_Event_Array_resize(struct IO_Event_Array *array, size_t count)
67
84
  {
68
85
  if (count <= array->count) {
86
+ // Already big enough:
69
87
  return 0;
70
88
  }
71
89
 
72
- // Compute the next multiple (ideally a power of 2):
90
+ if (count > IO_EVENT_ARRAY_MAXIMUM_COUNT) {
91
+ errno = ENOMEM;
92
+ return -1;
93
+ }
94
+
73
95
  size_t new_count = array->count;
74
- while (new_count < count) {
96
+
97
+ // If the array is empty, we need to set the initial size:
98
+ if (new_count == 0) new_count = IO_EVENT_ARRAY_DEFAULT_COUNT;
99
+ else while (new_count < count) {
100
+ // Ensure we don't overflow:
101
+ if (new_count > (IO_EVENT_ARRAY_MAXIMUM_COUNT / 2)) {
102
+ new_count = IO_EVENT_ARRAY_MAXIMUM_COUNT;
103
+ break;
104
+ }
105
+
106
+ // Compute the next multiple (ideally a power of 2):
75
107
  new_count *= 2;
76
108
  }
77
109
 
@@ -87,6 +119,7 @@ inline static int IO_Event_Array_resize(struct IO_Event_Array *array, size_t cou
87
119
  array->base = (void**)new_base;
88
120
  array->count = new_count;
89
121
 
122
+ // Resizing sucessful:
90
123
  return 1;
91
124
  }
92
125
 
@@ -107,6 +140,7 @@ inline static void* IO_Event_Array_lookup(struct IO_Event_Array *array, size_t i
107
140
  // Allocate the element if it doesn't exist:
108
141
  if (*element == NULL) {
109
142
  *element = malloc(array->element_size);
143
+ assert(*element);
110
144
 
111
145
  if (array->element_initialize) {
112
146
  array->element_initialize(*element);
@@ -34,8 +34,6 @@ enum {
34
34
  DEBUG = 0,
35
35
  };
36
36
 
37
- static VALUE IO_Event_Selector_EPoll = Qnil;
38
-
39
37
  enum {EPOLL_MAX_EVENTS = 64};
40
38
 
41
39
  // This represents an actual fiber waiting for a specific event.
@@ -184,7 +182,7 @@ static const rb_data_type_t IO_Event_Selector_EPoll_Type = {
184
182
  .dsize = IO_Event_Selector_EPoll_Type_size,
185
183
  },
186
184
  .data = NULL,
187
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
185
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
188
186
  };
189
187
 
190
188
  inline static
@@ -243,7 +241,7 @@ int IO_Event_Selector_EPoll_Descriptor_update(struct IO_Event_Selector_EPoll *se
243
241
  } else {
244
242
  // The IO has changed, we need to reset the state:
245
243
  epoll_descriptor->registered_events = 0;
246
- epoll_descriptor->io = io;
244
+ RB_OBJ_WRITE(selector->backend.self, &epoll_descriptor->io, io);
247
245
  }
248
246
 
249
247
  if (epoll_descriptor->waiting_events == 0) {
@@ -253,7 +251,7 @@ int IO_Event_Selector_EPoll_Descriptor_update(struct IO_Event_Selector_EPoll *se
253
251
  epoll_descriptor->registered_events = 0;
254
252
  }
255
253
 
256
- epoll_descriptor->io = 0;
254
+ RB_OBJ_WRITE(selector->backend.self, &epoll_descriptor->io, 0);
257
255
 
258
256
  return 0;
259
257
  }
@@ -333,13 +331,16 @@ VALUE IO_Event_Selector_EPoll_allocate(VALUE self) {
333
331
  struct IO_Event_Selector_EPoll *selector = NULL;
334
332
  VALUE instance = TypedData_Make_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, selector);
335
333
 
336
- IO_Event_Selector_initialize(&selector->backend, Qnil);
334
+ IO_Event_Selector_initialize(&selector->backend, self, Qnil);
337
335
  selector->descriptor = -1;
338
336
  selector->blocked = 0;
339
337
 
340
338
  selector->descriptors.element_initialize = IO_Event_Selector_EPoll_Descriptor_initialize;
341
339
  selector->descriptors.element_free = IO_Event_Selector_EPoll_Descriptor_free;
342
- IO_Event_Array_allocate(&selector->descriptors, 1024, sizeof(struct IO_Event_Selector_EPoll_Descriptor));
340
+ int result = IO_Event_Array_allocate(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_EPoll_Descriptor));
341
+ if (result < 0) {
342
+ rb_sys_fail("IO_Event_Selector_EPoll_allocate:IO_Event_Array_allocate");
343
+ }
343
344
 
344
345
  return instance;
345
346
  }
@@ -363,7 +364,7 @@ VALUE IO_Event_Selector_EPoll_initialize(VALUE self, VALUE loop) {
363
364
  struct IO_Event_Selector_EPoll *selector = NULL;
364
365
  TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, selector);
365
366
 
366
- IO_Event_Selector_initialize(&selector->backend, loop);
367
+ IO_Event_Selector_initialize(&selector->backend, self, loop);
367
368
  int result = epoll_create1(EPOLL_CLOEXEC);
368
369
 
369
370
  if (result == -1) {
@@ -503,13 +504,22 @@ VALUE IO_Event_Selector_EPoll_process_wait(VALUE self, VALUE fiber, VALUE _pid,
503
504
 
504
505
  rb_update_max_fd(descriptor);
505
506
 
507
+ // `pidfd_open` (above) may be edge triggered, so we need to check if the process is already exited, and if so, return immediately, otherwise we will block indefinitely.
508
+ VALUE status = IO_Event_Selector_process_status_wait(pid, flags);
509
+ if (status != Qnil) {
510
+ close(descriptor);
511
+ return status;
512
+ }
513
+
506
514
  struct IO_Event_Selector_EPoll_Waiting waiting = {
507
515
  .list = {.type = &IO_Event_Selector_EPoll_process_wait_list_type},
508
516
  .fiber = fiber,
509
517
  .events = IO_EVENT_READABLE,
510
518
  };
511
519
 
512
- int result = IO_Event_Selector_EPoll_Waiting_register(selector, 0, descriptor, &waiting);
520
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
521
+
522
+ int result = IO_Event_Selector_EPoll_Waiting_register(selector, _pid, descriptor, &waiting);
513
523
 
514
524
  if (result == -1) {
515
525
  close(descriptor);
@@ -568,6 +578,8 @@ VALUE IO_Event_Selector_EPoll_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE e
568
578
  .events = RB_NUM2INT(events),
569
579
  };
570
580
 
581
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
582
+
571
583
  int result = IO_Event_Selector_EPoll_Waiting_register(selector, io, descriptor, &waiting);
572
584
 
573
585
  if (result == -1) {
@@ -666,6 +678,8 @@ VALUE IO_Event_Selector_EPoll_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
666
678
  .offset = offset,
667
679
  };
668
680
 
681
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
682
+
669
683
  return rb_ensure(io_read_loop, (VALUE)&io_read_arguments, io_read_ensure, (VALUE)&io_read_arguments);
670
684
  }
671
685
 
@@ -762,6 +776,8 @@ VALUE IO_Event_Selector_EPoll_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
762
776
  .offset = offset,
763
777
  };
764
778
 
779
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
780
+
765
781
  return rb_ensure(io_write_loop, (VALUE)&io_write_arguments, io_write_ensure, (VALUE)&io_write_arguments);
766
782
  }
767
783
 
@@ -1037,8 +1053,7 @@ VALUE IO_Event_Selector_EPoll_wakeup(VALUE self) {
1037
1053
  }
1038
1054
 
1039
1055
  void Init_IO_Event_Selector_EPoll(VALUE IO_Event_Selector) {
1040
- IO_Event_Selector_EPoll = rb_define_class_under(IO_Event_Selector, "EPoll", rb_cObject);
1041
- rb_gc_register_mark_object(IO_Event_Selector_EPoll);
1056
+ VALUE IO_Event_Selector_EPoll = rb_define_class_under(IO_Event_Selector, "EPoll", rb_cObject);
1042
1057
 
1043
1058
  rb_define_alloc_func(IO_Event_Selector_EPoll, IO_Event_Selector_EPoll_allocate);
1044
1059
  rb_define_method(IO_Event_Selector_EPoll, "initialize", IO_Event_Selector_EPoll_initialize, 1);
@@ -43,8 +43,6 @@ enum {
43
43
  #define IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT
44
44
  #endif
45
45
 
46
- static VALUE IO_Event_Selector_KQueue = Qnil;
47
-
48
46
  enum {KQUEUE_MAX_EVENTS = 64};
49
47
 
50
48
  // This represents an actual fiber waiting for a specific event.
@@ -183,7 +181,7 @@ static const rb_data_type_t IO_Event_Selector_KQueue_Type = {
183
181
  .dsize = IO_Event_Selector_KQueue_Type_size,
184
182
  },
185
183
  .data = NULL,
186
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
184
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
187
185
  };
188
186
 
189
187
  inline static
@@ -307,13 +305,17 @@ VALUE IO_Event_Selector_KQueue_allocate(VALUE self) {
307
305
  struct IO_Event_Selector_KQueue *selector = NULL;
308
306
  VALUE instance = TypedData_Make_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
309
307
 
310
- IO_Event_Selector_initialize(&selector->backend, Qnil);
308
+ IO_Event_Selector_initialize(&selector->backend, self, Qnil);
311
309
  selector->descriptor = -1;
312
310
  selector->blocked = 0;
313
311
 
314
312
  selector->descriptors.element_initialize = IO_Event_Selector_KQueue_Descriptor_initialize;
315
313
  selector->descriptors.element_free = IO_Event_Selector_KQueue_Descriptor_free;
316
- IO_Event_Array_allocate(&selector->descriptors, 1024, sizeof(struct IO_Event_Selector_KQueue_Descriptor));
314
+
315
+ int result = IO_Event_Array_allocate(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_KQueue_Descriptor));
316
+ if (result < 0) {
317
+ rb_sys_fail("IO_Event_Selector_KQueue_allocate:IO_Event_Array_allocate");
318
+ }
317
319
 
318
320
  return instance;
319
321
  }
@@ -340,7 +342,7 @@ VALUE IO_Event_Selector_KQueue_initialize(VALUE self, VALUE loop) {
340
342
  struct IO_Event_Selector_KQueue *selector = NULL;
341
343
  TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
342
344
 
343
- IO_Event_Selector_initialize(&selector->backend, loop);
345
+ IO_Event_Selector_initialize(&selector->backend, self, loop);
344
346
  int result = kqueue();
345
347
 
346
348
  if (result == -1) {
@@ -459,6 +461,7 @@ void process_prewait(pid_t pid) {
459
461
  result = waitid(P_PID, pid, &info, WEXITED | WNOWAIT);
460
462
  // This can sometimes get interrupted by SIGCHLD.
461
463
  } while (result == -1 && errno == EINTR);
464
+
462
465
  if (result == -1) {
463
466
  rb_sys_fail("process_prewait:waitid");
464
467
  }
@@ -503,6 +506,8 @@ VALUE IO_Event_Selector_KQueue_process_wait(VALUE self, VALUE fiber, VALUE _pid,
503
506
  .events = IO_EVENT_EXIT,
504
507
  };
505
508
 
509
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
510
+
506
511
  struct process_wait_arguments process_wait_arguments = {
507
512
  .selector = selector,
508
513
  .waiting = &waiting,
@@ -566,6 +571,8 @@ VALUE IO_Event_Selector_KQueue_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE
566
571
  .events = RB_NUM2INT(events),
567
572
  };
568
573
 
574
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
575
+
569
576
  int result = IO_Event_Selector_KQueue_Waiting_register(selector, descriptor, &waiting);
570
577
  if (result == -1) {
571
578
  rb_sys_fail("IO_Event_Selector_KQueue_io_wait:IO_Event_Selector_KQueue_Waiting_register");
@@ -669,6 +676,8 @@ VALUE IO_Event_Selector_KQueue_io_read(VALUE self, VALUE fiber, VALUE io, VALUE
669
676
  .offset = offset,
670
677
  };
671
678
 
679
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
680
+
672
681
  return rb_ensure(io_read_loop, (VALUE)&io_read_arguments, io_read_ensure, (VALUE)&io_read_arguments);
673
682
  }
674
683
 
@@ -775,6 +784,8 @@ VALUE IO_Event_Selector_KQueue_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
775
784
  .offset = offset,
776
785
  };
777
786
 
787
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
788
+
778
789
  return rb_ensure(io_write_loop, (VALUE)&io_write_arguments, io_write_ensure, (VALUE)&io_write_arguments);
779
790
  }
780
791
 
@@ -981,7 +992,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
981
992
  // Non-comprehensive testing shows this gives a 1.5x speedup.
982
993
 
983
994
  // First do the syscall with no timeout to get any immediately available events:
984
- if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl timeout=" PRINTF_TIMESPEC "\r\n", PRINTF_TIMESPEC_ARGS(arguments.storage));
995
+ if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl timeout=" IO_EVENT_PRINTF_TIMESPEC "\r\n", IO_EVENT_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage));
985
996
  select_internal_with_gvl(&arguments);
986
997
  if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl done\r\n");
987
998
 
@@ -999,7 +1010,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
999
1010
  struct timespec start_time;
1000
1011
  IO_Event_Selector_current_time(&start_time);
1001
1012
 
1002
- if (DEBUG) fprintf(stderr, "IO_Event_Selector_KQueue_select timeout=" PRINTF_TIMESPEC "\n", PRINTF_TIMESPEC_ARGS(arguments.storage));
1013
+ if (DEBUG) fprintf(stderr, "IO_Event_Selector_KQueue_select timeout=" IO_EVENT_PRINTF_TIMESPEC "\n", IO_EVENT_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage));
1003
1014
  select_internal_without_gvl(&arguments);
1004
1015
 
1005
1016
  struct timespec end_time;
@@ -1052,8 +1063,7 @@ VALUE IO_Event_Selector_KQueue_wakeup(VALUE self) {
1052
1063
  }
1053
1064
 
1054
1065
  void Init_IO_Event_Selector_KQueue(VALUE IO_Event_Selector) {
1055
- IO_Event_Selector_KQueue = rb_define_class_under(IO_Event_Selector, "KQueue", rb_cObject);
1056
- rb_gc_register_mark_object(IO_Event_Selector_KQueue);
1066
+ VALUE IO_Event_Selector_KQueue = rb_define_class_under(IO_Event_Selector, "KQueue", rb_cObject);
1057
1067
 
1058
1068
  rb_define_alloc_func(IO_Event_Selector_KQueue, IO_Event_Selector_KQueue_allocate);
1059
1069
  rb_define_method(IO_Event_Selector_KQueue, "initialize", IO_Event_Selector_KQueue_initialize, 1);
@@ -38,6 +38,7 @@ inline static void IO_Event_List_append(struct IO_Event_List *list, struct IO_Ev
38
38
  head->tail = node;
39
39
  }
40
40
 
41
+ // Prepend an item to the beginning of the list.
41
42
  inline static void IO_Event_List_prepend(struct IO_Event_List *list, struct IO_Event_List *node)
42
43
  {
43
44
  assert(node->head == NULL);
@@ -64,6 +65,7 @@ inline static void IO_Event_List_pop(struct IO_Event_List *node)
64
65
  node->head = node->tail = NULL;
65
66
  }
66
67
 
68
+ // Remove an item from the list, if it is in a list.
67
69
  inline static void IO_Event_List_free(struct IO_Event_List *node)
68
70
  {
69
71
  if (node->head && node->tail) {
@@ -71,11 +73,27 @@ inline static void IO_Event_List_free(struct IO_Event_List *node)
71
73
  }
72
74
  }
73
75
 
74
- inline static int IO_Event_List_empty(struct IO_Event_List *list)
76
+ // Calculate the memory size of the list nodes.
77
+ inline static size_t IO_Event_List_memory_size(const struct IO_Event_List *list)
78
+ {
79
+ size_t memsize = 0;
80
+
81
+ const struct IO_Event_List *node = list->tail;
82
+ while (node != list) {
83
+ memsize += sizeof(struct IO_Event_List);
84
+ node = node->tail;
85
+ }
86
+
87
+ return memsize;
88
+ }
89
+
90
+ // Return true if the list is empty.
91
+ inline static int IO_Event_List_empty(const struct IO_Event_List *list)
75
92
  {
76
93
  return list->head == list->tail;
77
94
  }
78
95
 
96
+ // Enumerate all items in the list, assuming the list will not be modified during iteration.
79
97
  inline static void IO_Event_List_immutable_each(struct IO_Event_List *list, void (*callback)(struct IO_Event_List *node))
80
98
  {
81
99
  struct IO_Event_List *node = list->tail;
@@ -172,24 +172,35 @@ static void queue_pop(struct IO_Event_Selector *backend, struct IO_Event_Selecto
172
172
  if (waiting->head) {
173
173
  waiting->head->tail = waiting->tail;
174
174
  } else {
175
+ // We must have been at the head of the queue:
175
176
  backend->waiting = waiting->tail;
176
177
  }
177
178
 
178
179
  if (waiting->tail) {
179
180
  waiting->tail->head = waiting->head;
180
181
  } else {
182
+ // We must have been at the tail of the queue:
181
183
  backend->ready = waiting->head;
182
184
  }
185
+
186
+ waiting->head = NULL;
187
+ waiting->tail = NULL;
183
188
  }
184
189
 
185
190
  static void queue_push(struct IO_Event_Selector *backend, struct IO_Event_Selector_Queue *waiting) {
191
+ assert(waiting->head == NULL);
192
+ assert(waiting->tail == NULL);
193
+
186
194
  if (backend->waiting) {
195
+ // If there was an item in the queue already, we shift it along:
187
196
  backend->waiting->head = waiting;
188
197
  waiting->tail = backend->waiting;
189
198
  } else {
199
+ // If the queue was empty, we update the tail too:
190
200
  backend->ready = waiting;
191
201
  }
192
202
 
203
+ // We always push to the front/head:
193
204
  backend->waiting = waiting;
194
205
  }
195
206
 
@@ -222,6 +233,8 @@ VALUE IO_Event_Selector_resume(struct IO_Event_Selector *backend, int argc, VALU
222
233
  .fiber = rb_fiber_current()
223
234
  };
224
235
 
236
+ RB_OBJ_WRITTEN(backend->self, Qundef, waiting.fiber);
237
+
225
238
  queue_push(backend, &waiting);
226
239
 
227
240
  struct wait_and_transfer_arguments arguments = {
@@ -255,6 +268,8 @@ VALUE IO_Event_Selector_raise(struct IO_Event_Selector *backend, int argc, VALUE
255
268
  .fiber = rb_fiber_current()
256
269
  };
257
270
 
271
+ RB_OBJ_WRITTEN(backend->self, Qundef, waiting.fiber);
272
+
258
273
  queue_push(backend, &waiting);
259
274
 
260
275
  struct wait_and_transfer_arguments arguments = {
@@ -270,11 +285,13 @@ VALUE IO_Event_Selector_raise(struct IO_Event_Selector *backend, int argc, VALUE
270
285
  void IO_Event_Selector_queue_push(struct IO_Event_Selector *backend, VALUE fiber)
271
286
  {
272
287
  struct IO_Event_Selector_Queue *waiting = malloc(sizeof(struct IO_Event_Selector_Queue));
288
+ assert(waiting);
273
289
 
274
290
  waiting->head = NULL;
275
291
  waiting->tail = NULL;
276
292
  waiting->flags = IO_EVENT_SELECTOR_QUEUE_INTERNAL;
277
- waiting->fiber = fiber;
293
+
294
+ RB_OBJ_WRITE(backend->self, &waiting->fiber, fiber);
278
295
 
279
296
  queue_push(backend, waiting);
280
297
  }
@@ -283,9 +300,10 @@ static inline
283
300
  void IO_Event_Selector_queue_pop(struct IO_Event_Selector *backend, struct IO_Event_Selector_Queue *ready)
284
301
  {
285
302
  if (DEBUG) fprintf(stderr, "IO_Event_Selector_queue_pop -> %p\n", (void*)ready->fiber);
303
+
286
304
  if (ready->flags & IO_EVENT_SELECTOR_QUEUE_FIBER) {
287
305
  IO_Event_Selector_fiber_transfer(ready->fiber, 0, NULL);
288
- } else {
306
+ } else if (ready->flags & IO_EVENT_SELECTOR_QUEUE_INTERNAL) {
289
307
  VALUE fiber = ready->fiber;
290
308
  queue_pop(backend, ready);
291
309
  free(ready);
@@ -293,6 +311,8 @@ void IO_Event_Selector_queue_pop(struct IO_Event_Selector *backend, struct IO_Ev
293
311
  if (RTEST(rb_funcall(fiber, id_alive_p, 0))) {
294
312
  rb_funcall(fiber, id_transfer, 0);
295
313
  }
314
+ } else {
315
+ rb_raise(rb_eRuntimeError, "Unknown queue type!");
296
316
  }
297
317
  }
298
318
 
@@ -300,6 +320,8 @@ int IO_Event_Selector_queue_flush(struct IO_Event_Selector *backend)
300
320
  {
301
321
  int count = 0;
302
322
 
323
+ // During iteration of the queue, the same item may be re-queued. If we don't handle this correctly, we may end up in an infinite loop. So, to avoid this situation, we keep note of the current head of the queue and break the loop if we reach the same item again.
324
+
303
325
  // Get the current tail and head of the queue:
304
326
  struct IO_Event_Selector_Queue *waiting = backend->waiting;
305
327
  if (DEBUG) fprintf(stderr, "IO_Event_Selector_queue_flush waiting = %p\n", waiting);
@@ -95,27 +95,29 @@ struct IO_Event_Selector_Queue {
95
95
  };
96
96
 
97
97
  struct IO_Event_Selector {
98
+ VALUE self;
98
99
  VALUE loop;
99
100
 
100
- struct IO_Event_Selector_Queue *free;
101
-
102
- // Append to waiting.
101
+ // Append to waiting (front/head of queue).
103
102
  struct IO_Event_Selector_Queue *waiting;
104
- // Process from ready.
103
+ // Process from ready (back/tail of queue).
105
104
  struct IO_Event_Selector_Queue *ready;
106
105
  };
107
106
 
108
107
  static inline
109
- void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE loop) {
110
- backend->loop = loop;
108
+ void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, VALUE loop) {
109
+ RB_OBJ_WRITE(self, &backend->self, self);
110
+ RB_OBJ_WRITE(self, &backend->loop, loop);
111
111
  backend->waiting = NULL;
112
112
  backend->ready = NULL;
113
113
  }
114
114
 
115
115
  static inline
116
116
  void IO_Event_Selector_mark(struct IO_Event_Selector *backend) {
117
+ rb_gc_mark_movable(backend->self);
117
118
  rb_gc_mark_movable(backend->loop);
118
119
 
120
+ // Walk backwards through the ready queue:
119
121
  struct IO_Event_Selector_Queue *ready = backend->ready;
120
122
  while (ready) {
121
123
  rb_gc_mark_movable(ready->fiber);
@@ -125,6 +127,7 @@ void IO_Event_Selector_mark(struct IO_Event_Selector *backend) {
125
127
 
126
128
  static inline
127
129
  void IO_Event_Selector_compact(struct IO_Event_Selector *backend) {
130
+ backend->self = rb_gc_location(backend->self);
128
131
  backend->loop = rb_gc_location(backend->loop);
129
132
 
130
133
  struct IO_Event_Selector_Queue *ready = backend->ready;
@@ -149,5 +152,5 @@ int IO_Event_Selector_queue_flush(struct IO_Event_Selector *backend);
149
152
  void IO_Event_Selector_elapsed_time(struct timespec* start, struct timespec* stop, struct timespec *duration);
150
153
  void IO_Event_Selector_current_time(struct timespec *time);
151
154
 
152
- #define PRINTF_TIMESPEC "%lld.%.9ld"
153
- #define PRINTF_TIMESPEC_ARGS(ts) (long long)((ts).tv_sec), (ts).tv_nsec
155
+ #define IO_EVENT_PRINTF_TIMESPEC "%lld.%.9ld"
156
+ #define IO_EVENT_PRINTF_TIMESPEC_ARGUMENTS(ts) (long long)((ts).tv_sec), (ts).tv_nsec