io-event 0.1.0 → 0.2.3

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: d311214a03ab6047c7ba6952dfeeef690a453d0bd661df5e5419f477832b3cbd
4
- data.tar.gz: 51653482b73275b35b8ac590b5576d2d991170c463d540fd3e5b6dbd8c2d4555
3
+ metadata.gz: c0978ed0fd5d85399283cd8c4bb296f44662af2fad9a67e992000f3214898f5a
4
+ data.tar.gz: 154503dd7f0f52dc2ad166d262bb371f81fc60e148dd0c74eadb65051bc5e71f
5
5
  SHA512:
6
- metadata.gz: e9f333103f29c2e2a69f1573fefb11c01a23d2cf9cc56e30343e9e9f9ec6f5bd5f44408a95cbdd84bf9350b7eb7e3d2ea57df92702a97407e49ab7a6006b9506
7
- data.tar.gz: a35949d9fb40a0361b2627f3d998dfae230474a9fcb51f6361c0d3686560cc3d22749147e9a581dfc8f429ffd35b80d920a780400d66398e4de9c113d4122f8e
6
+ metadata.gz: a54cab01e6c314890284d9640e3404f6a34706402b2cbf6383bff69c6c341c0c84c56f64d1feebb46e875539e1baab95749ce7f41e5f15ae13af0d8b9e1e0c1a
7
+ data.tar.gz: 7b2891a70e1703616f104410d4f86898a5d9c2bfa6c8ecde0295bea1bfed6ee7efada50568bdf8f841e0360db370d209259f66f2cb17f97d2be47db8a1c2e101
data/ext/IO_Event.bundle CHANGED
Binary file
data/ext/Makefile CHANGED
@@ -139,8 +139,8 @@ target_prefix =
139
139
  LOCAL_LIBS =
140
140
  LIBS =
141
141
  ORIG_SRCS =
142
- SRCS = $(ORIG_SRCS) event.c selector.c kqueue.c
143
- OBJS = event.o selector.o kqueue.o
142
+ SRCS = $(ORIG_SRCS) event.c selector.c kqueue.c interrupt.c
143
+ OBJS = event.o selector.o kqueue.o interrupt.o
144
144
  HDRS = $(srcdir)/extconf.h
145
145
  LOCAL_HDRS =
146
146
  TARGET = IO_Event
data/ext/extconf.rb CHANGED
@@ -48,6 +48,9 @@ if have_header('sys/event.h')
48
48
  $srcs << "io/event/selector/kqueue.c"
49
49
  end
50
50
 
51
+ have_header('sys/eventfd.h')
52
+ $srcs << "io/event/interrupt.c"
53
+
51
54
  have_func("rb_io_descriptor")
52
55
  have_func("&rb_process_status_wait")
53
56
  have_func('rb_fiber_current')
data/ext/interrupt.o ADDED
Binary file
@@ -0,0 +1,108 @@
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;
22
+
23
+ #include "interrupt.h"
24
+ #include <unistd.h>
25
+
26
+ #include "selector/selector.h"
27
+
28
+ #ifdef HAVE_SYS_EVENTFD_H
29
+ #include <sys/eventfd.h>
30
+
31
+ void IO_Event_Interrupt_open(struct IO_Event_Interrupt *interrupt)
32
+ {
33
+ interrupt->descriptor = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
34
+ rb_update_max_fd(interrupt->descriptor);
35
+ }
36
+
37
+ void IO_Event_Interrupt_close(struct IO_Event_Interrupt *interrupt)
38
+ {
39
+ close(interrupt->descriptor);
40
+ }
41
+
42
+ void IO_Event_Interrupt_signal(struct IO_Event_Interrupt *interrupt)
43
+ {
44
+ uint64_t value = 1;
45
+ ssize_t result = write(interrupt->descriptor, &value, sizeof(value));
46
+
47
+ if (result == -1) {
48
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return;
49
+
50
+ rb_sys_fail("IO_Event_Interrupt_signal:write");
51
+ }
52
+ }
53
+
54
+ void IO_Event_Interrupt_clear(struct IO_Event_Interrupt *interrupt)
55
+ {
56
+ uint64_t value = 0;
57
+ ssize_t result = read(interrupt->descriptor, &value, sizeof(value));
58
+
59
+ if (result == -1) {
60
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return;
61
+
62
+ rb_sys_fail("IO_Event_Interrupt_clear:read");
63
+ }
64
+ }
65
+ #else
66
+ void IO_Event_Interrupt_open(struct IO_Event_Interrupt *interrupt)
67
+ {
68
+ #ifdef __linux__
69
+ pipe2(interrupt->descriptor, O_CLOEXEC | O_NONBLOCK);
70
+ #else
71
+ pipe(interrupt->descriptor);
72
+ IO_Event_Selector_nonblock_set(interrupt->descriptor[0]);
73
+ IO_Event_Selector_nonblock_set(interrupt->descriptor[1]);
74
+ #endif
75
+
76
+ rb_update_max_fd(interrupt->descriptor[0]);
77
+ rb_update_max_fd(interrupt->descriptor[1]);
78
+ }
79
+
80
+ void IO_Event_Interrupt_close(struct IO_Event_Interrupt *interrupt)
81
+ {
82
+ close(interrupt->descriptor[0]);
83
+ close(interrupt->descriptor[1]);
84
+ }
85
+
86
+ void IO_Event_Interrupt_signal(struct IO_Event_Interrupt *interrupt)
87
+ {
88
+ ssize_t result = write(interrupt->descriptor[1], ".", 1);
89
+
90
+ if (result == -1) {
91
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return;
92
+
93
+ rb_sys_fail("IO_Event_Interrupt_signal:write");
94
+ }
95
+ }
96
+
97
+ void IO_Event_Interrupt_clear(struct IO_Event_Interrupt *interrupt)
98
+ {
99
+ char buffer[128];
100
+ ssize_t result = read(interrupt->descriptor[0], buffer, sizeof(buffer));
101
+
102
+ if (result == -1) {
103
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return;
104
+
105
+ rb_sys_fail("IO_Event_Interrupt_clear:read");
106
+ }
107
+ }
108
+ #endif
@@ -0,0 +1,47 @@
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
+ #pragma once
22
+
23
+ #include "extconf.h"
24
+
25
+ #ifdef HAVE_SYS_EVENTFD_H
26
+ struct IO_Event_Interrupt {
27
+ int descriptor;
28
+ };
29
+
30
+ inline int IO_Event_Interrupt_descriptor(struct IO_Event_Interrupt *interrupt) {
31
+ return interrupt->descriptor;
32
+ }
33
+ #else
34
+ struct IO_Event_Interrupt {
35
+ int descriptor[2];
36
+ };
37
+
38
+ inline int IO_Event_Interrupt_descriptor(struct IO_Event_Interrupt *interrupt) {
39
+ return interrupt->descriptor[0];
40
+ }
41
+ #endif
42
+
43
+ void IO_Event_Interrupt_open(struct IO_Event_Interrupt *interrupt);
44
+ void IO_Event_Interrupt_close(struct IO_Event_Interrupt *interrupt);
45
+
46
+ void IO_Event_Interrupt_signal(struct IO_Event_Interrupt *interrupt);
47
+ void IO_Event_Interrupt_clear(struct IO_Event_Interrupt *interrupt);
@@ -26,6 +26,9 @@
26
26
  #include <errno.h>
27
27
 
28
28
  #include "pidfd.c"
29
+ #include "../interrupt.h"
30
+
31
+ static const int DEBUG = 0;
29
32
 
30
33
  static VALUE IO_Event_Selector_EPoll = Qnil;
31
34
 
@@ -34,6 +37,8 @@ enum {EPOLL_MAX_EVENTS = 64};
34
37
  struct IO_Event_Selector_EPoll {
35
38
  struct IO_Event_Selector backend;
36
39
  int descriptor;
40
+ int blocked;
41
+ struct IO_Event_Interrupt interrupt;
37
42
  };
38
43
 
39
44
  void IO_Event_Selector_EPoll_Type_mark(void *_data)
@@ -47,6 +52,8 @@ void close_internal(struct IO_Event_Selector_EPoll *data) {
47
52
  if (data->descriptor >= 0) {
48
53
  close(data->descriptor);
49
54
  data->descriptor = -1;
55
+
56
+ IO_Event_Interrupt_close(&data->interrupt);
50
57
  }
51
58
  }
52
59
 
@@ -85,6 +92,21 @@ VALUE IO_Event_Selector_EPoll_allocate(VALUE self) {
85
92
  return instance;
86
93
  }
87
94
 
95
+ void IO_Event_Interrupt_add(struct IO_Event_Interrupt *interrupt, struct IO_Event_Selector_EPoll *data) {
96
+ int descriptor = IO_Event_Interrupt_descriptor(interrupt);
97
+
98
+ struct epoll_event event = {
99
+ .events = EPOLLIN|EPOLLRDHUP,
100
+ .data = {.ptr = NULL},
101
+ };
102
+
103
+ int result = epoll_ctl(data->descriptor, EPOLL_CTL_ADD, descriptor, &event);
104
+
105
+ if (result == -1) {
106
+ rb_sys_fail("IO_Event_Interrupt_add:epoll_ctl");
107
+ }
108
+ }
109
+
88
110
  VALUE IO_Event_Selector_EPoll_initialize(VALUE self, VALUE loop) {
89
111
  struct IO_Event_Selector_EPoll *data = NULL;
90
112
  TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, data);
@@ -100,9 +122,19 @@ VALUE IO_Event_Selector_EPoll_initialize(VALUE self, VALUE loop) {
100
122
  rb_update_max_fd(data->descriptor);
101
123
  }
102
124
 
125
+ IO_Event_Interrupt_open(&data->interrupt);
126
+ IO_Event_Interrupt_add(&data->interrupt, data);
127
+
103
128
  return self;
104
129
  }
105
130
 
131
+ VALUE IO_Event_Selector_EPoll_loop(VALUE self) {
132
+ struct IO_Event_Selector_EPoll *data = NULL;
133
+ TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, data);
134
+
135
+ return data->backend.loop;
136
+ }
137
+
106
138
  VALUE IO_Event_Selector_EPoll_close(VALUE self) {
107
139
  struct IO_Event_Selector_EPoll *data = NULL;
108
140
  TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, data);
@@ -498,7 +530,9 @@ void * select_internal(void *_arguments) {
498
530
 
499
531
  static
500
532
  void select_internal_without_gvl(struct select_arguments *arguments) {
533
+ arguments->data->blocked = 1;
501
534
  rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
535
+ arguments->data->blocked = 0;
502
536
 
503
537
  if (arguments->count == -1) {
504
538
  rb_sys_fail("select_internal_without_gvl:epoll_wait");
@@ -525,29 +559,54 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
525
559
  .timeout = 0
526
560
  };
527
561
 
562
+ // Process any currently pending events:
528
563
  select_internal_with_gvl(&arguments);
529
-
530
- // If the ready list was empty and no events were processed:
531
- if (!ready && arguments.count == 0) {
564
+
565
+ // If we:
566
+ // 1. Didn't process any ready fibers, and
567
+ // 2. Didn't process any events from non-blocking select (above), and
568
+ // 3. There are no items in the ready list,
569
+ // then we can perform a blocking select.
570
+ if (!ready && !arguments.count && !data->backend.ready) {
532
571
  arguments.timeout = make_timeout(duration);
533
572
 
534
573
  if (arguments.timeout != 0) {
574
+ // Wait for events to occur
535
575
  select_internal_without_gvl(&arguments);
536
576
  }
537
577
  }
538
578
 
539
579
  for (int i = 0; i < arguments.count; i += 1) {
540
- VALUE fiber = (VALUE)arguments.events[i].data.ptr;
541
- VALUE result = INT2NUM(arguments.events[i].events);
580
+ const struct epoll_event *event = &arguments.events[i];
581
+ if (DEBUG) fprintf(stderr, "-> ptr=%p events=%d\n", event->data.ptr, event->events);
542
582
 
543
- // fprintf(stderr, "-> fiber=%p descriptor=%d\n", (void*)fiber, events[i].data.fd);
544
-
545
- IO_Event_Selector_fiber_transfer(fiber, 1, &result);
583
+ if (event->data.ptr) {
584
+ VALUE fiber = (VALUE)event->data.ptr;
585
+ VALUE result = INT2NUM(event->events);
586
+
587
+ IO_Event_Selector_fiber_transfer(fiber, 1, &result);
588
+ } else {
589
+ IO_Event_Interrupt_clear(&data->interrupt);
590
+ }
546
591
  }
547
592
 
548
593
  return INT2NUM(arguments.count);
549
594
  }
550
595
 
596
+ VALUE IO_Event_Selector_EPoll_wakeup(VALUE self) {
597
+ struct IO_Event_Selector_EPoll *data = NULL;
598
+ TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, data);
599
+
600
+ // If we are blocking, we can schedule a nop event to wake up the selector:
601
+ if (data->blocked) {
602
+ IO_Event_Interrupt_signal(&data->interrupt);
603
+
604
+ return Qtrue;
605
+ }
606
+
607
+ return Qfalse;
608
+ }
609
+
551
610
  void Init_IO_Event_Selector_EPoll(VALUE IO_Event_Selector) {
552
611
  IO_Event_Selector_EPoll = rb_define_class_under(IO_Event_Selector, "EPoll", rb_cObject);
553
612
  rb_gc_register_mark_object(IO_Event_Selector_EPoll);
@@ -555,6 +614,8 @@ void Init_IO_Event_Selector_EPoll(VALUE IO_Event_Selector) {
555
614
  rb_define_alloc_func(IO_Event_Selector_EPoll, IO_Event_Selector_EPoll_allocate);
556
615
  rb_define_method(IO_Event_Selector_EPoll, "initialize", IO_Event_Selector_EPoll_initialize, 1);
557
616
 
617
+ rb_define_method(IO_Event_Selector_EPoll, "loop", IO_Event_Selector_EPoll_loop, 0);
618
+
558
619
  rb_define_method(IO_Event_Selector_EPoll, "transfer", IO_Event_Selector_EPoll_transfer, 0);
559
620
  rb_define_method(IO_Event_Selector_EPoll, "resume", IO_Event_Selector_EPoll_resume, -1);
560
621
  rb_define_method(IO_Event_Selector_EPoll, "yield", IO_Event_Selector_EPoll_yield, 0);
@@ -564,6 +625,7 @@ void Init_IO_Event_Selector_EPoll(VALUE IO_Event_Selector) {
564
625
  rb_define_method(IO_Event_Selector_EPoll, "ready?", IO_Event_Selector_EPoll_ready_p, 0);
565
626
 
566
627
  rb_define_method(IO_Event_Selector_EPoll, "select", IO_Event_Selector_EPoll_select, 1);
628
+ rb_define_method(IO_Event_Selector_EPoll, "wakeup", IO_Event_Selector_EPoll_wakeup, 0);
567
629
  rb_define_method(IO_Event_Selector_EPoll, "close", IO_Event_Selector_EPoll_close, 0);
568
630
 
569
631
  rb_define_method(IO_Event_Selector_EPoll, "io_wait", IO_Event_Selector_EPoll_io_wait, 3);
@@ -20,6 +20,8 @@
20
20
 
21
21
  #pragma once
22
22
 
23
+ #include <ruby.h>
24
+
23
25
  #define IO_EVENT_SELECTOR_EPOLL
24
26
 
25
27
  void Init_IO_Event_Selector_EPoll(VALUE IO_Event_Selector);
@@ -33,6 +33,8 @@ enum {KQUEUE_MAX_EVENTS = 64};
33
33
  struct IO_Event_Selector_KQueue {
34
34
  struct IO_Event_Selector backend;
35
35
  int descriptor;
36
+
37
+ int blocked;
36
38
  };
37
39
 
38
40
  void IO_Event_Selector_KQueue_Type_mark(void *_data)
@@ -80,6 +82,7 @@ VALUE IO_Event_Selector_KQueue_allocate(VALUE self) {
80
82
 
81
83
  IO_Event_Selector_initialize(&data->backend, Qnil);
82
84
  data->descriptor = -1;
85
+ data->blocked = 0;
83
86
 
84
87
  return instance;
85
88
  }
@@ -103,6 +106,13 @@ VALUE IO_Event_Selector_KQueue_initialize(VALUE self, VALUE loop) {
103
106
  return self;
104
107
  }
105
108
 
109
+ VALUE IO_Event_Selector_KQueue_loop(VALUE self) {
110
+ struct IO_Event_Selector_KQueue *data = NULL;
111
+ TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, data);
112
+
113
+ return data->backend.loop;
114
+ }
115
+
106
116
  VALUE IO_Event_Selector_KQueue_close(VALUE self) {
107
117
  struct IO_Event_Selector_KQueue *data = NULL;
108
118
  TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, data);
@@ -561,7 +571,10 @@ void * select_internal(void *_arguments) {
561
571
 
562
572
  static
563
573
  void select_internal_without_gvl(struct select_arguments *arguments) {
574
+ arguments->data->blocked = 1;
575
+
564
576
  rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
577
+ arguments->data->blocked = 0;
565
578
 
566
579
  if (arguments->count == -1) {
567
580
  rb_sys_fail("select_internal_without_gvl:kevent");
@@ -592,18 +605,23 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
592
605
  }
593
606
  };
594
607
 
608
+ arguments.timeout = &arguments.storage;
609
+
595
610
  // We break this implementation into two parts.
596
611
  // (1) count = kevent(..., timeout = 0)
597
612
  // (2) without gvl: kevent(..., timeout = 0) if count == 0 and timeout != 0
598
613
  // This allows us to avoid releasing and reacquiring the GVL.
599
614
  // Non-comprehensive testing shows this gives a 1.5x speedup.
600
- arguments.timeout = &arguments.storage;
601
615
 
602
616
  // First do the syscall with no timeout to get any immediately available events:
603
617
  select_internal_with_gvl(&arguments);
604
618
 
605
- // If there were no pending events, if we have a timeout, wait for more events:
606
- if (!ready && arguments.count == 0) {
619
+ // If we:
620
+ // 1. Didn't process any ready fibers, and
621
+ // 2. Didn't process any events from non-blocking select (above), and
622
+ // 3. There are no items in the ready list,
623
+ // then we can perform a blocking select.
624
+ if (!ready && !arguments.count && !data->backend.ready) {
607
625
  arguments.timeout = make_timeout(duration, &arguments.storage);
608
626
 
609
627
  if (!timeout_nonblocking(arguments.timeout)) {
@@ -614,15 +632,40 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
614
632
  }
615
633
 
616
634
  for (int i = 0; i < arguments.count; i += 1) {
617
- VALUE fiber = (VALUE)arguments.events[i].udata;
618
- VALUE result = INT2NUM(arguments.events[i].filter);
619
-
620
- IO_Event_Selector_fiber_transfer(fiber, 1, &result);
635
+ if (arguments.events[i].udata) {
636
+ VALUE fiber = (VALUE)arguments.events[i].udata;
637
+ VALUE result = INT2NUM(arguments.events[i].filter);
638
+
639
+ IO_Event_Selector_fiber_transfer(fiber, 1, &result);
640
+ }
621
641
  }
622
642
 
623
643
  return INT2NUM(arguments.count);
624
644
  }
625
645
 
646
+ VALUE IO_Event_Selector_KQueue_wakeup(VALUE self) {
647
+ struct IO_Event_Selector_KQueue *data = NULL;
648
+ TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, data);
649
+
650
+ if (data->blocked) {
651
+ struct kevent trigger = {0};
652
+
653
+ trigger.filter = EVFILT_USER;
654
+ trigger.flags = EV_ADD|EV_CLEAR;
655
+ trigger.fflags = NOTE_TRIGGER;
656
+
657
+ int result = kevent(data->descriptor, &trigger, 1, NULL, 0, NULL);
658
+
659
+ if (result == -1) {
660
+ rb_sys_fail("IO_Event_Selector_KQueue_wakeup:kevent");
661
+ }
662
+
663
+ return Qtrue;
664
+ }
665
+
666
+ return Qfalse;
667
+ }
668
+
626
669
  void Init_IO_Event_Selector_KQueue(VALUE IO_Event_Selector) {
627
670
  IO_Event_Selector_KQueue = rb_define_class_under(IO_Event_Selector, "KQueue", rb_cObject);
628
671
  rb_gc_register_mark_object(IO_Event_Selector_KQueue);
@@ -630,6 +673,8 @@ void Init_IO_Event_Selector_KQueue(VALUE IO_Event_Selector) {
630
673
  rb_define_alloc_func(IO_Event_Selector_KQueue, IO_Event_Selector_KQueue_allocate);
631
674
  rb_define_method(IO_Event_Selector_KQueue, "initialize", IO_Event_Selector_KQueue_initialize, 1);
632
675
 
676
+ rb_define_method(IO_Event_Selector_KQueue, "loop", IO_Event_Selector_KQueue_loop, 0);
677
+
633
678
  rb_define_method(IO_Event_Selector_KQueue, "transfer", IO_Event_Selector_KQueue_transfer, 0);
634
679
  rb_define_method(IO_Event_Selector_KQueue, "resume", IO_Event_Selector_KQueue_resume, -1);
635
680
  rb_define_method(IO_Event_Selector_KQueue, "yield", IO_Event_Selector_KQueue_yield, 0);
@@ -639,6 +684,7 @@ void Init_IO_Event_Selector_KQueue(VALUE IO_Event_Selector) {
639
684
  rb_define_method(IO_Event_Selector_KQueue, "ready?", IO_Event_Selector_KQueue_ready_p, 0);
640
685
 
641
686
  rb_define_method(IO_Event_Selector_KQueue, "select", IO_Event_Selector_KQueue_select, 1);
687
+ rb_define_method(IO_Event_Selector_KQueue, "wakeup", IO_Event_Selector_KQueue_wakeup, 0);
642
688
  rb_define_method(IO_Event_Selector_KQueue, "close", IO_Event_Selector_KQueue_close, 0);
643
689
 
644
690
  rb_define_method(IO_Event_Selector_KQueue, "io_wait", IO_Event_Selector_KQueue_io_wait, 3);
@@ -18,6 +18,8 @@
18
18
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  // THE SOFTWARE.
20
20
 
21
+ #pragma once
22
+
21
23
  #include <ruby.h>
22
24
  #include <ruby/thread.h>
23
25
  #include <ruby/io.h>
@@ -37,6 +37,7 @@ struct IO_Event_Selector_URing {
37
37
  struct IO_Event_Selector backend;
38
38
  struct io_uring ring;
39
39
  size_t pending;
40
+ int blocked;
40
41
  };
41
42
 
42
43
  void IO_Event_Selector_URing_Type_mark(void *_data)
@@ -86,7 +87,8 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) {
86
87
  data->ring.ring_fd = -1;
87
88
 
88
89
  data->pending = 0;
89
-
90
+ data->blocked = 0;
91
+
90
92
  return instance;
91
93
  }
92
94
 
@@ -106,6 +108,13 @@ VALUE IO_Event_Selector_URing_initialize(VALUE self, VALUE loop) {
106
108
  return self;
107
109
  }
108
110
 
111
+ VALUE IO_Event_Selector_URing_loop(VALUE self) {
112
+ struct IO_Event_Selector_URing *data = NULL;
113
+ TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, data);
114
+
115
+ return data->backend.loop;
116
+ }
117
+
109
118
  VALUE IO_Event_Selector_URing_close(VALUE self) {
110
119
  struct IO_Event_Selector_URing *data = NULL;
111
120
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, data);
@@ -156,7 +165,8 @@ VALUE IO_Event_Selector_URing_raise(int argc, VALUE *argv, VALUE self)
156
165
 
157
166
  return IO_Event_Selector_raise(&data->backend, argc, argv);
158
167
  }
159
-
168
+
169
+ int blocked;
160
170
  VALUE IO_Event_Selector_URing_ready_p(VALUE self) {
161
171
  struct IO_Event_Selector_URing *data = NULL;
162
172
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, data);
@@ -171,14 +181,14 @@ int io_uring_submit_flush(struct IO_Event_Selector_URing *data) {
171
181
 
172
182
  // Try to submit:
173
183
  int result = io_uring_submit(&data->ring);
174
-
184
+
175
185
  if (result >= 0) {
176
186
  // If it was submitted, reset pending count:
177
187
  data->pending = 0;
178
188
  } else if (result != -EBUSY && result != -EAGAIN) {
179
189
  rb_syserr_fail(-result, "io_uring_submit_flush");
180
190
  }
181
-
191
+
182
192
  return result;
183
193
  }
184
194
 
@@ -194,7 +204,7 @@ int io_uring_submit_now(struct IO_Event_Selector_URing *data) {
194
204
  data->pending = 0;
195
205
  return result;
196
206
  }
197
-
207
+
198
208
  if (result == -EBUSY || result == -EAGAIN) {
199
209
  IO_Event_Selector_yield(&data->backend);
200
210
  } else {
@@ -214,7 +224,7 @@ struct io_uring_sqe * io_get_sqe(struct IO_Event_Selector_URing *data) {
214
224
  while (sqe == NULL) {
215
225
  // The submit queue is full, we need to drain it:
216
226
  io_uring_submit_now(data);
217
-
227
+
218
228
  sqe = io_uring_get_sqe(&data->ring);
219
229
  }
220
230
 
@@ -523,10 +533,8 @@ struct select_arguments {
523
533
  static
524
534
  void * select_internal(void *_arguments) {
525
535
  struct select_arguments * arguments = (struct select_arguments *)_arguments;
526
-
527
- io_uring_submit_flush(arguments->data);
528
-
529
536
  struct io_uring_cqe *cqe = NULL;
537
+
530
538
  arguments->result = io_uring_wait_cqe_timeout(&arguments->data->ring, &cqe, arguments->timeout);
531
539
 
532
540
  return NULL;
@@ -534,7 +542,11 @@ void * select_internal(void *_arguments) {
534
542
 
535
543
  static
536
544
  int select_internal_without_gvl(struct select_arguments *arguments) {
545
+ io_uring_submit_flush(arguments->data);
546
+
547
+ arguments->data->blocked = 1;
537
548
  rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
549
+ arguments->data->blocked = 0;
538
550
 
539
551
  if (arguments->result == -ETIME) {
540
552
  arguments->result = 0;
@@ -548,6 +560,8 @@ int select_internal_without_gvl(struct select_arguments *arguments) {
548
560
  return arguments->result;
549
561
  }
550
562
 
563
+ // #define IO_EVENT_SELECTOR_URING_UDATA_INTERRUPT ((__u64) -2)
564
+
551
565
  static inline
552
566
  unsigned select_process_completions(struct io_uring *ring) {
553
567
  unsigned completed = 0;
@@ -563,6 +577,10 @@ unsigned select_process_completions(struct io_uring *ring) {
563
577
  continue;
564
578
  }
565
579
 
580
+ // if (cqe->user_data == IO_EVENT_SELECTOR_URING_UDATA_INTERRUPT) {
581
+ // io_uring_cq_advance(ring, 1);
582
+ // }
583
+
566
584
  VALUE fiber = (VALUE)cqe->user_data;
567
585
  VALUE result = RB_INT2NUM(cqe->res);
568
586
 
@@ -587,9 +605,13 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
587
605
  int ready = IO_Event_Selector_queue_flush(&data->backend);
588
606
 
589
607
  int result = select_process_completions(&data->ring);
590
-
591
- // If the ready list was empty and we didn't process any completions:
592
- if (!ready && result == 0) {
608
+
609
+ // If we:
610
+ // 1. Didn't process any ready fibers, and
611
+ // 2. Didn't process any events from non-blocking select (above), and
612
+ // 3. There are no items in the ready list,
613
+ // then we can perform a blocking select.
614
+ if (!ready && !result && !data->backend.ready) {
593
615
  // We might need to wait for events:
594
616
  struct select_arguments arguments = {
595
617
  .data = data,
@@ -605,7 +627,7 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
605
627
  // The timeout specified required "nonblocking" behaviour so we just flush the SQ if required:
606
628
  io_uring_submit_flush(data);
607
629
  }
608
-
630
+
609
631
  // After waiting/flushing the SQ, check if there are any completions:
610
632
  result = select_process_completions(&data->ring);
611
633
  }
@@ -613,6 +635,33 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
613
635
  return RB_INT2NUM(result);
614
636
  }
615
637
 
638
+ VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
639
+ struct IO_Event_Selector_URing *data = NULL;
640
+ TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, data);
641
+
642
+ // If we are blocking, we can schedule a nop event to wake up the selector:
643
+ if (data->blocked) {
644
+ struct io_uring_sqe *sqe = NULL;
645
+
646
+ while (true) {
647
+ sqe = io_uring_get_sqe(&data->ring);
648
+ if (sqe) break;
649
+
650
+ rb_thread_schedule();
651
+
652
+ // It's possible we became unblocked already, so we can assume the selector has already cycled at least once:
653
+ if (!data->blocked) return Qfalse;
654
+ }
655
+
656
+ io_uring_prep_nop(sqe);
657
+ io_uring_submit(&data->ring);
658
+
659
+ return Qtrue;
660
+ }
661
+
662
+ return Qfalse;
663
+ }
664
+
616
665
  void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
617
666
  IO_Event_Selector_URing = rb_define_class_under(IO_Event_Selector, "URing", rb_cObject);
618
667
  rb_gc_register_mark_object(IO_Event_Selector_URing);
@@ -620,6 +669,8 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
620
669
  rb_define_alloc_func(IO_Event_Selector_URing, IO_Event_Selector_URing_allocate);
621
670
  rb_define_method(IO_Event_Selector_URing, "initialize", IO_Event_Selector_URing_initialize, 1);
622
671
 
672
+ rb_define_method(IO_Event_Selector_URing, "loop", IO_Event_Selector_URing_loop, 0);
673
+
623
674
  rb_define_method(IO_Event_Selector_URing, "transfer", IO_Event_Selector_URing_transfer, 0);
624
675
  rb_define_method(IO_Event_Selector_URing, "resume", IO_Event_Selector_URing_resume, -1);
625
676
  rb_define_method(IO_Event_Selector_URing, "yield", IO_Event_Selector_URing_yield, 0);
@@ -629,6 +680,7 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
629
680
  rb_define_method(IO_Event_Selector_URing, "ready?", IO_Event_Selector_URing_ready_p, 0);
630
681
 
631
682
  rb_define_method(IO_Event_Selector_URing, "select", IO_Event_Selector_URing_select, 1);
683
+ rb_define_method(IO_Event_Selector_URing, "wakeup", IO_Event_Selector_URing_wakeup, 0);
632
684
  rb_define_method(IO_Event_Selector_URing, "close", IO_Event_Selector_URing_close, 0);
633
685
 
634
686
  rb_define_method(IO_Event_Selector_URing, "io_wait", IO_Event_Selector_URing_io_wait, 3);
@@ -25,4 +25,3 @@
25
25
  #define IO_EVENT_SELECTOR_URING
26
26
 
27
27
  void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector);
28
- VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration);
data/ext/kqueue.o CHANGED
Binary file
data/ext/mkmf.log CHANGED
@@ -113,6 +113,22 @@ checked program was:
113
113
 
114
114
  --------------------
115
115
 
116
+ have_header: checking for sys/eventfd.h... -------------------- no
117
+
118
+ "clang -E -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0/arm64-darwin21 -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0/ruby/backward -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0 -I. -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -pipe -Wall conftest.c -o conftest.i"
119
+ conftest.c:3:10: fatal error: 'sys/eventfd.h' file not found
120
+ #include <sys/eventfd.h>
121
+ ^~~~~~~~~~~~~~~
122
+ 1 error generated.
123
+ checked program was:
124
+ /* begin */
125
+ 1: #include "ruby.h"
126
+ 2:
127
+ 3: #include <sys/eventfd.h>
128
+ /* end */
129
+
130
+ --------------------
131
+
116
132
  have_func: checking for rb_io_descriptor()... -------------------- no
117
133
 
118
134
  "clang -fdeclspec -o conftest -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0/arm64-darwin21 -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0/ruby/backward -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0 -I. -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -pipe -Wall conftest.c -L. -L/Users/samuel/.rubies/ruby-3.0.3/lib -L/opt/local/lib -L. -fstack-protector-strong -L/opt/local/lib -m64 -lruby.3.0-static -framework Security -framework Foundation -lpthread -lgmp -ldl -lobjc "
@@ -141,7 +157,7 @@ checked program was:
141
157
  "clang -fdeclspec -o conftest -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0/arm64-darwin21 -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0/ruby/backward -I/Users/samuel/.rubies/ruby-3.0.3/include/ruby-3.0.0 -I. -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -pipe -Wall conftest.c -L. -L/Users/samuel/.rubies/ruby-3.0.3/lib -L/opt/local/lib -L. -fstack-protector-strong -L/opt/local/lib -m64 -lruby.3.0-static -framework Security -framework Foundation -lpthread -lgmp -ldl -lobjc "
142
158
  Undefined symbols for architecture arm64:
143
159
  "_rb_io_descriptor", referenced from:
144
- _t in conftest-3f9e69.o
160
+ _t in conftest-1e4d1a.o
145
161
  ld: symbol(s) not found for architecture arm64
146
162
  clang: error: linker command failed with exit code 1 (use -v to see invocation)
147
163
  checked program was:
@@ -28,6 +28,14 @@ module IO::Event
28
28
  @readable = {}
29
29
  @writable = {}
30
30
  @priority = {}
31
+
32
+ unless Fiber.current == selector.loop
33
+ raise "Selector must be initialized on event loop fiber!"
34
+ end
35
+ end
36
+
37
+ def wakeup
38
+ @selector.wakeup
31
39
  end
32
40
 
33
41
  def close
@@ -86,13 +94,17 @@ module IO::Event
86
94
  end
87
95
 
88
96
  def select(duration = nil)
97
+ unless Fiber.current == @selector.loop
98
+ raise "Selector must be run on event loop fiber!"
99
+ end
100
+
89
101
  @selector.select(duration)
90
102
  end
91
103
 
92
104
  private
93
105
 
94
106
  def register_readable(fiber, io, events)
95
- if (events & READABLE) > 0
107
+ if (events & IO::READABLE) > 0
96
108
  if @readable.key?(io)
97
109
  raise "Cannot wait for #{io} to become readable from multiple fibers."
98
110
  end
@@ -110,7 +122,7 @@ module IO::Event
110
122
  end
111
123
 
112
124
  def register_writable(fiber, io, events)
113
- if (events & WRITABLE) > 0
125
+ if (events & IO::WRITABLE) > 0
114
126
  if @writable.key?(io)
115
127
  raise "Cannot wait for #{io} to become writable from multiple fibers."
116
128
  end
@@ -128,7 +140,7 @@ module IO::Event
128
140
  end
129
141
 
130
142
  def register_priority(fiber, io, events)
131
- if (events & PRIORITY) > 0
143
+ if (events & IO::PRIORITY) > 0
132
144
  if @priority.key?(io)
133
145
  raise "Cannot wait for #{io} to become priority from multiple fibers."
134
146
  end
@@ -0,0 +1,57 @@
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
+ module IO::Event
22
+ # A thread safe synchronisation primative.
23
+ class Interrupt
24
+ def self.attach(selector)
25
+ self.new(selector)
26
+ end
27
+
28
+ def initialize(selector)
29
+ @selector = selector
30
+ @input, @output = ::IO.pipe
31
+
32
+ @fiber = Fiber.new do
33
+ while true
34
+ if @selector.io_wait(@fiber, @input, IO::READABLE)
35
+ @input.read_nonblock(1)
36
+ end
37
+ end
38
+ end
39
+
40
+ @fiber.transfer
41
+ end
42
+
43
+ # Send a sigle byte interrupt.
44
+ def signal
45
+ @output.write('.')
46
+ @output.flush
47
+ end
48
+
49
+ def close
50
+ @input.close
51
+ @output.close
52
+ # @fiber.raise(::Interrupt)
53
+ end
54
+ end
55
+
56
+ private_constant :Interrupt
57
+ end
@@ -18,19 +18,39 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative '../interrupt'
22
+
21
23
  module IO::Event
22
24
  module Selector
23
25
  class Select
24
26
  def initialize(loop)
25
27
  @loop = loop
26
28
 
27
- @readable = {}
28
- @writable = {}
29
+ @readable = Hash.new.compare_by_identity
30
+ @writable = Hash.new.compare_by_identity
31
+
32
+ @blocked = false
29
33
 
30
34
  @ready = Queue.new
35
+ @interrupt = Interrupt.attach(self)
36
+ end
37
+
38
+ attr :loop
39
+
40
+ # If the event loop is currently blocked,
41
+ def wakeup
42
+ if @blocked
43
+ @interrupt.signal
44
+
45
+ return true
46
+ end
47
+
48
+ return false
31
49
  end
32
50
 
33
51
  def close
52
+ @interrupt.close
53
+
34
54
  @loop = nil
35
55
  @readable = nil
36
56
  @writable = nil
@@ -97,12 +117,12 @@ module IO::Event
97
117
  def io_wait(fiber, io, events)
98
118
  remove_readable = remove_writable = false
99
119
 
100
- if (events & READABLE) > 0 or (events & PRIORITY) > 0
120
+ if (events & IO::READABLE) > 0 or (events & IO::PRIORITY) > 0
101
121
  @readable[io] = fiber
102
122
  remove_readable = true
103
123
  end
104
124
 
105
- if (events & WRITABLE) > 0
125
+ if (events & IO::WRITABLE) > 0
106
126
  @writable[io] = fiber
107
127
  remove_writable = true
108
128
  end
@@ -123,9 +143,9 @@ module IO::Event
123
143
 
124
144
  case result = io.read_nonblock(maximum_size, exception: false)
125
145
  when :wait_readable
126
- self.io_wait(fiber, io, READABLE)
146
+ self.io_wait(fiber, io, IO::READABLE)
127
147
  when :wait_writable
128
- self.io_wait(fiber, io, WRITABLE)
148
+ self.io_wait(fiber, io, IO::WRITABLE)
129
149
  else
130
150
  break unless result
131
151
 
@@ -147,9 +167,9 @@ module IO::Event
147
167
  chunk = buffer.to_str(offset, length)
148
168
  case result = io.write_nonblock(chunk, exception: false)
149
169
  when :wait_readable
150
- self.io_wait(fiber, io, READABLE)
170
+ self.io_wait(fiber, io, IO::READABLE)
151
171
  when :wait_writable
152
- self.io_wait(fiber, io, WRITABLE)
172
+ self.io_wait(fiber, io, IO::WRITABLE)
153
173
  else
154
174
  offset += result
155
175
  length -= result
@@ -169,7 +189,7 @@ module IO::Event
169
189
  w.close
170
190
  end
171
191
 
172
- self.io_wait(fiber, r, READABLE)
192
+ self.io_wait(fiber, r, IO::READABLE)
173
193
 
174
194
  return thread.value
175
195
  ensure
@@ -193,26 +213,32 @@ module IO::Event
193
213
 
194
214
  def select(duration = nil)
195
215
  if pop_ready
216
+ # If we have popped items from the ready list, they may influence the duration calculation, so we don't delay the event loop:
196
217
  duration = 0
197
218
  end
198
219
 
220
+ @blocked = true
221
+ duration = 0 unless @ready.empty?
199
222
  readable, writable, _ = ::IO.select(@readable.keys, @writable.keys, nil, duration)
223
+ @blocked = false
200
224
 
201
225
  ready = Hash.new(0)
202
226
 
203
227
  readable&.each do |io|
204
228
  fiber = @readable.delete(io)
205
- ready[fiber] |= READABLE
229
+ ready[fiber] |= IO::READABLE
206
230
  end
207
231
 
208
232
  writable&.each do |io|
209
233
  fiber = @writable.delete(io)
210
- ready[fiber] |= WRITABLE
234
+ ready[fiber] |= IO::WRITABLE
211
235
  end
212
236
 
213
237
  ready.each do |fiber, events|
214
238
  fiber.transfer(events) if fiber.alive?
215
239
  end
240
+
241
+ return ready.size
216
242
  end
217
243
  end
218
244
  end
@@ -21,11 +21,6 @@
21
21
  require_relative 'selector/select'
22
22
 
23
23
  module IO::Event
24
- # These constants are the same as those defined in IO.
25
- READABLE = 1
26
- PRIORITY = 2
27
- WRITABLE = 4
28
-
29
24
  module Selector
30
25
  def self.default(env = ENV)
31
26
  if name = env['IO_EVENT_SELECTOR']&.to_sym
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module IO::Event
22
- VERSION = "0.1.0"
22
+ VERSION = "0.2.3"
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-06 00:00:00.000000000 Z
11
+ date: 2021-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bake
@@ -53,33 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec
56
+ name: sus
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.0'
61
+ version: '0.5'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: timers
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
68
+ version: '0.5'
83
69
  description:
84
70
  email:
85
71
  executables: []
@@ -92,8 +78,11 @@ files:
92
78
  - ext/event.o
93
79
  - ext/extconf.h
94
80
  - ext/extconf.rb
81
+ - ext/interrupt.o
95
82
  - ext/io/event/event.c
96
83
  - ext/io/event/event.h
84
+ - ext/io/event/interrupt.c
85
+ - ext/io/event/interrupt.h
97
86
  - ext/io/event/selector/epoll.c
98
87
  - ext/io/event/selector/epoll.h
99
88
  - ext/io/event/selector/kqueue.c
@@ -108,6 +97,7 @@ files:
108
97
  - ext/selector.o
109
98
  - lib/io/event.rb
110
99
  - lib/io/event/debug/selector.rb
100
+ - lib/io/event/interrupt.rb
111
101
  - lib/io/event/selector.rb
112
102
  - lib/io/event/selector/select.rb
113
103
  - lib/io/event/version.rb