event 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/event/backend/backend.h +27 -0
- data/ext/event/backend/epoll.c +60 -26
- data/ext/event/backend/kqueue.c +38 -22
- data/ext/event/backend/uring.c +57 -35
- data/lib/event/backend/select.rb +5 -5
- data/lib/event/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 820b9a8295d89939388b1fa56c818addaad4e46a3af90eb0318a6c4d4338b729
|
4
|
+
data.tar.gz: a925d6a51efeacb281c68cdb51840fe81a1c97aa22297c684f7a441d9ee2afc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a591c0f50e2f1fa19d895eea4a94141b6d6ed2f359325f61400f67c35a51e1f89ff4a8806ae764042f0e747612a0c291554bed423c83ae9ec58afbc24bf20c3b
|
7
|
+
data.tar.gz: e32064f234f542aee8bff87e19e1c3647c08e054c962d4943674e7a17167860f5898bc76b1c134494fea1a46a8f2a40da0b30d5516470950eecd02df470893c8
|
@@ -0,0 +1,27 @@
|
|
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
|
+
enum Event {
|
22
|
+
READABLE = 1,
|
23
|
+
PRIORITY = 2,
|
24
|
+
WRITABLE = 4,
|
25
|
+
ERROR = 8,
|
26
|
+
HANGUP = 16
|
27
|
+
};
|
data/ext/event/backend/epoll.c
CHANGED
@@ -19,6 +19,7 @@
|
|
19
19
|
// THE SOFTWARE.
|
20
20
|
|
21
21
|
#include "kqueue.h"
|
22
|
+
#include "backend.h"
|
22
23
|
|
23
24
|
#include <sys/epoll.h>
|
24
25
|
#include <time.h>
|
@@ -27,8 +28,6 @@
|
|
27
28
|
static VALUE Event_Backend_EPoll = Qnil;
|
28
29
|
static ID id_fileno, id_transfer;
|
29
30
|
|
30
|
-
static const int READABLE = 1, PRIORITY = 2, WRITABLE = 4;
|
31
|
-
|
32
31
|
static const unsigned EPOLL_MAX_EVENTS = 1024;
|
33
32
|
|
34
33
|
struct Event_Backend_EPoll {
|
@@ -97,32 +96,66 @@ VALUE Event_Backend_EPoll_initialize(VALUE self, VALUE loop) {
|
|
97
96
|
return self;
|
98
97
|
}
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
static inline
|
100
|
+
uint32_t epoll_flags_from_events(int events) {
|
101
|
+
uint32_t flags = 0;
|
103
102
|
|
104
|
-
|
103
|
+
if (events & READABLE) flags |= EPOLLIN;
|
104
|
+
if (events & PRIORITY) flags |= EPOLLPRI;
|
105
|
+
if (events & WRITABLE) flags |= EPOLLOUT;
|
105
106
|
|
106
|
-
|
107
|
-
|
107
|
+
flags |= EPOLLRDHUP;
|
108
|
+
flags |= EPOLLONESHOT;
|
109
|
+
|
110
|
+
return flags;
|
111
|
+
}
|
112
|
+
|
113
|
+
static inline
|
114
|
+
int events_from_epoll_flags(uint32_t flags) {
|
115
|
+
int events = 0;
|
108
116
|
|
109
|
-
|
117
|
+
if (flags & EPOLLIN) events |= READABLE;
|
118
|
+
if (flags & EPOLLPRI) events |= PRIORITY;
|
119
|
+
if (flags & EPOLLOUT) events |= WRITABLE;
|
110
120
|
|
111
|
-
|
112
|
-
|
113
|
-
|
121
|
+
return events;
|
122
|
+
}
|
123
|
+
|
124
|
+
struct io_wait_arguments {
|
125
|
+
struct Event_Backend_EPoll *data;
|
126
|
+
int duplicate;
|
127
|
+
};
|
128
|
+
|
129
|
+
static
|
130
|
+
VALUE io_wait_ensure(VALUE _arguments) {
|
131
|
+
struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
|
114
132
|
|
115
|
-
if (
|
116
|
-
|
133
|
+
if (arguments->duplicate >= 0) {
|
134
|
+
close(arguments->duplicate);
|
117
135
|
}
|
118
136
|
|
119
|
-
|
120
|
-
|
121
|
-
|
137
|
+
return Qnil;
|
138
|
+
};
|
139
|
+
|
140
|
+
static
|
141
|
+
VALUE io_wait_transfer(VALUE _arguments) {
|
142
|
+
struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
|
143
|
+
|
144
|
+
VALUE result = rb_funcall(arguments->data->loop, id_transfer, 0);
|
145
|
+
|
146
|
+
return INT2NUM(events_from_epoll_flags(NUM2INT(result)));
|
147
|
+
};
|
148
|
+
|
149
|
+
VALUE Event_Backend_EPoll_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE events) {
|
150
|
+
struct Event_Backend_EPoll *data = NULL;
|
151
|
+
TypedData_Get_Struct(self, struct Event_Backend_EPoll, &Event_Backend_EPoll_Type, data);
|
152
|
+
|
153
|
+
struct epoll_event event = {0};
|
122
154
|
|
123
|
-
|
124
|
-
|
155
|
+
int descriptor = NUM2INT(rb_funcall(io, id_fileno, 0));
|
156
|
+
int duplicate = -1;
|
125
157
|
|
158
|
+
event.events = epoll_flags_from_events(NUM2INT(events));
|
126
159
|
event.data.ptr = (void*)fiber;
|
127
160
|
|
128
161
|
// A better approach is to batch all changes:
|
@@ -144,13 +177,12 @@ VALUE Event_Backend_EPoll_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE event
|
|
144
177
|
rb_sys_fail("epoll_ctl");
|
145
178
|
}
|
146
179
|
|
147
|
-
|
180
|
+
struct io_wait_arguments io_wait_arguments = {
|
181
|
+
.data = data,
|
182
|
+
.duplicate = duplicate
|
183
|
+
};
|
148
184
|
|
149
|
-
|
150
|
-
close(duplicate);
|
151
|
-
}
|
152
|
-
|
153
|
-
return Qnil;
|
185
|
+
return rb_ensure(io_wait_transfer, (VALUE)&io_wait_arguments, io_wait_ensure, (VALUE)&io_wait_arguments);
|
154
186
|
}
|
155
187
|
|
156
188
|
static
|
@@ -186,7 +218,9 @@ VALUE Event_Backend_EPoll_select(VALUE self, VALUE duration) {
|
|
186
218
|
|
187
219
|
for (int i = 0; i < count; i += 1) {
|
188
220
|
VALUE fiber = (VALUE)events[i].data.ptr;
|
189
|
-
|
221
|
+
VALUE result = INT2NUM(events[i].events);
|
222
|
+
|
223
|
+
rb_funcall(fiber, id_transfer, 1, result);
|
190
224
|
}
|
191
225
|
|
192
226
|
return INT2NUM(count);
|
data/ext/event/backend/kqueue.c
CHANGED
@@ -19,6 +19,7 @@
|
|
19
19
|
// THE SOFTWARE.
|
20
20
|
|
21
21
|
#include "kqueue.h"
|
22
|
+
#include "backend.h"
|
22
23
|
|
23
24
|
#include <sys/event.h>
|
24
25
|
#include <sys/ioctl.h>
|
@@ -27,7 +28,12 @@
|
|
27
28
|
static VALUE Event_Backend_KQueue = Qnil;
|
28
29
|
static ID id_fileno, id_transfer;
|
29
30
|
|
30
|
-
static const int
|
31
|
+
static const int
|
32
|
+
READABLE = 1,
|
33
|
+
PRIORITY = 2,
|
34
|
+
WRITABLE = 4,
|
35
|
+
ERROR = 8,
|
36
|
+
HANGUP = 16;
|
31
37
|
|
32
38
|
static const unsigned KQUEUE_MAX_EVENTS = 1024;
|
33
39
|
|
@@ -98,30 +104,40 @@ VALUE Event_Backend_KQueue_initialize(VALUE self, VALUE loop) {
|
|
98
104
|
return self;
|
99
105
|
}
|
100
106
|
|
107
|
+
static inline
|
108
|
+
u_short kqueue_filter_from_events(int events) {
|
109
|
+
u_short filter = 0;
|
110
|
+
|
111
|
+
if (events & READABLE) filter |= EVFILT_READ;
|
112
|
+
if (events & PRIORITY) filter |= EV_OOBAND;
|
113
|
+
if (events & WRITABLE) filter |= EVFILT_WRITE;
|
114
|
+
|
115
|
+
return filter;
|
116
|
+
}
|
117
|
+
|
118
|
+
static inline
|
119
|
+
int events_from_kqueue_filter(u_short filter) {
|
120
|
+
int events = 0;
|
121
|
+
|
122
|
+
if (filter & EVFILT_READ) events |= READABLE;
|
123
|
+
if (filter & EV_OOBAND) events |= PRIORITY;
|
124
|
+
if (filter & EVFILT_WRITE) events |= WRITABLE;
|
125
|
+
|
126
|
+
return INT2NUM(events);
|
127
|
+
}
|
128
|
+
|
101
129
|
VALUE Event_Backend_KQueue_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE events) {
|
102
130
|
struct Event_Backend_KQueue *data = NULL;
|
103
131
|
TypedData_Get_Struct(self, struct Event_Backend_KQueue, &Event_Backend_KQueue_Type, data);
|
104
132
|
|
105
|
-
struct kevent event;
|
106
|
-
u_short flags = 0;
|
133
|
+
struct kevent event = {0};
|
107
134
|
|
108
135
|
int descriptor = NUM2INT(rb_funcall(io, id_fileno, 0));
|
109
136
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
}
|
115
|
-
|
116
|
-
if (mask & PRIORITY) {
|
117
|
-
flags |= EV_OOBAND;
|
118
|
-
}
|
119
|
-
|
120
|
-
if (mask & WRITABLE) {
|
121
|
-
flags |= EVFILT_WRITE;
|
122
|
-
}
|
123
|
-
|
124
|
-
EV_SET(&event, descriptor, flags, EV_ADD|EV_ENABLE|EV_ONESHOT, 0, 0, (void*)fiber);
|
137
|
+
event.ident = descriptor;
|
138
|
+
event.filter = kqueue_filters_from_events(events);
|
139
|
+
event.flags = EV_ADD | EV_ENABLE | EV_ONESHOT;
|
140
|
+
event.udata = (void*)fiber;
|
125
141
|
|
126
142
|
// A better approach is to batch all changes:
|
127
143
|
int result = kevent(data->descriptor, &event, 1, NULL, 0, NULL);
|
@@ -130,9 +146,8 @@ VALUE Event_Backend_KQueue_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE even
|
|
130
146
|
rb_sys_fail("kevent");
|
131
147
|
}
|
132
148
|
|
133
|
-
rb_funcall(data->loop, id_transfer, 0);
|
134
|
-
|
135
|
-
return Qnil;
|
149
|
+
VALUE result = rb_funcall(data->loop, id_transfer, 0);
|
150
|
+
return INT2NUM(events_from_kqueue_filter(NUM2INT(result)));
|
136
151
|
}
|
137
152
|
|
138
153
|
static
|
@@ -176,7 +191,8 @@ VALUE Event_Backend_KQueue_select(VALUE self, VALUE duration) {
|
|
176
191
|
|
177
192
|
for (int i = 0; i < count; i += 1) {
|
178
193
|
VALUE fiber = (VALUE)events[i].udata;
|
179
|
-
|
194
|
+
VALUE result = INT2NUM(events[i].filter);
|
195
|
+
rb_funcall(fiber, id_transfer, 1, result);
|
180
196
|
}
|
181
197
|
|
182
198
|
return INT2NUM(count);
|
data/ext/event/backend/uring.c
CHANGED
@@ -19,15 +19,15 @@
|
|
19
19
|
// THE SOFTWARE.
|
20
20
|
|
21
21
|
#include "uring.h"
|
22
|
+
#include "backend.h"
|
22
23
|
|
23
24
|
#include <liburing.h>
|
25
|
+
#include <poll.h>
|
24
26
|
#include <time.h>
|
25
27
|
|
26
28
|
static VALUE Event_Backend_URing = Qnil;
|
27
29
|
static ID id_fileno, id_transfer;
|
28
30
|
|
29
|
-
static const int READABLE = 1, PRIORITY = 2, WRITABLE = 4;
|
30
|
-
|
31
31
|
static const int URING_ENTRIES = 1024;
|
32
32
|
static const int URING_MAX_EVENTS = 1024;
|
33
33
|
|
@@ -96,6 +96,31 @@ VALUE Event_Backend_URing_initialize(VALUE self, VALUE loop) {
|
|
96
96
|
return self;
|
97
97
|
}
|
98
98
|
|
99
|
+
static inline
|
100
|
+
short poll_flags_from_events(int events) {
|
101
|
+
short flags = 0;
|
102
|
+
|
103
|
+
if (events & READABLE) flags |= POLLIN;
|
104
|
+
if (events & PRIORITY) flags |= POLLPRI;
|
105
|
+
if (events & WRITABLE) flags |= POLLOUT;
|
106
|
+
|
107
|
+
flags |= POLLERR;
|
108
|
+
flags |= POLLHUP;
|
109
|
+
|
110
|
+
return flags;
|
111
|
+
}
|
112
|
+
|
113
|
+
static inline
|
114
|
+
int events_from_poll_flags(short flags) {
|
115
|
+
int events = 0;
|
116
|
+
|
117
|
+
if (flags & POLLIN) events |= READABLE;
|
118
|
+
if (flags & POLLPRI) events |= PRIORITY;
|
119
|
+
if (flags & POLLOUT) events |= WRITABLE;
|
120
|
+
|
121
|
+
return events;
|
122
|
+
}
|
123
|
+
|
99
124
|
VALUE Event_Backend_URing_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE events) {
|
100
125
|
struct Event_Backend_URing *data = NULL;
|
101
126
|
TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
|
@@ -103,32 +128,21 @@ VALUE Event_Backend_URing_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE event
|
|
103
128
|
int descriptor = NUM2INT(rb_funcall(io, id_fileno, 0));
|
104
129
|
struct io_uring_sqe *sqe = io_uring_get_sqe(data->ring);
|
105
130
|
|
106
|
-
|
107
|
-
short flags = 0;
|
108
|
-
|
109
|
-
if (mask & READABLE) {
|
110
|
-
flags |= POLL_IN;
|
111
|
-
}
|
112
|
-
|
113
|
-
if (mask & PRIORITY) {
|
114
|
-
flags |= POLL_PRI;
|
115
|
-
}
|
131
|
+
short flags = poll_flags_from_events(NUM2INT(events));
|
116
132
|
|
117
|
-
if (mask & WRITABLE) {
|
118
|
-
flags |= POLL_OUT;
|
119
|
-
}
|
120
|
-
|
121
133
|
// fprintf(stderr, "poll_add(%p, %d, %d)\n", sqe, descriptor, flags);
|
122
|
-
|
134
|
+
|
123
135
|
io_uring_prep_poll_add(sqe, descriptor, flags);
|
124
136
|
io_uring_sqe_set_data(sqe, (void*)fiber);
|
125
137
|
io_uring_submit(data->ring);
|
126
138
|
|
127
|
-
|
128
|
-
|
129
|
-
|
139
|
+
VALUE result = rb_funcall(data->loop, id_transfer, 0);
|
140
|
+
|
141
|
+
// We explicitly filter the resulting events based on the requested events.
|
142
|
+
// In some cases, poll will report events we didn't ask for.
|
143
|
+
flags &= NUM2INT(result);
|
130
144
|
|
131
|
-
return
|
145
|
+
return INT2NUM(events_from_poll_flags(flags));
|
132
146
|
}
|
133
147
|
|
134
148
|
static
|
@@ -196,7 +210,9 @@ VALUE Event_Backend_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffe
|
|
196
210
|
io_uring_prep_readv(sqe, descriptor, iovecs, 1, 0);
|
197
211
|
io_uring_sqe_set_data(sqe, (void*)fiber);
|
198
212
|
io_uring_submit(data->ring);
|
199
|
-
|
213
|
+
|
214
|
+
// fprintf(stderr, "prep_readv(%p, %d, %ld)\n", sqe, descriptor, iovecs[0].iov_len);
|
215
|
+
|
200
216
|
int result = NUM2INT(rb_funcall(data->loop, id_transfer, 0));
|
201
217
|
|
202
218
|
if (result < 0) {
|
@@ -227,6 +243,8 @@ VALUE Event_Backend_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buff
|
|
227
243
|
io_uring_sqe_set_data(sqe, (void*)fiber);
|
228
244
|
io_uring_submit(data->ring);
|
229
245
|
|
246
|
+
// fprintf(stderr, "prep_writev(%p, %d, %ld)\n", sqe, descriptor, iovecs[0].iov_len);
|
247
|
+
|
230
248
|
int result = NUM2INT(rb_funcall(data->loop, id_transfer, 0));
|
231
249
|
|
232
250
|
if (result < 0) {
|
@@ -242,33 +260,37 @@ VALUE Event_Backend_URing_select(VALUE self, VALUE duration) {
|
|
242
260
|
|
243
261
|
struct io_uring_cqe *cqes[URING_MAX_EVENTS];
|
244
262
|
struct __kernel_timespec storage;
|
245
|
-
|
246
|
-
|
247
|
-
|
263
|
+
|
264
|
+
int result = io_uring_peek_batch_cqe(data->ring, cqes, URING_MAX_EVENTS);
|
265
|
+
|
266
|
+
// fprintf(stderr, "result = %d\n", result);
|
267
|
+
|
268
|
+
if (result < 0) {
|
269
|
+
rb_syserr_fail(-result, strerror(-result));
|
270
|
+
} else if (result == 0) {
|
271
|
+
result = io_uring_wait_cqes(data->ring, cqes, 1, make_timeout(duration, &storage), NULL);
|
272
|
+
|
273
|
+
// fprintf(stderr, "result (timeout) = %d\n", result);
|
248
274
|
|
249
275
|
if (result == -ETIME) {
|
250
|
-
|
276
|
+
result = 0;
|
251
277
|
} else if (result < 0) {
|
252
278
|
rb_syserr_fail(-result, strerror(-result));
|
253
279
|
}
|
254
280
|
}
|
255
281
|
|
256
|
-
int
|
257
|
-
|
258
|
-
if (count == -1) {
|
259
|
-
rb_sys_fail("io_uring_peek_batch_cqe");
|
260
|
-
}
|
261
|
-
|
262
|
-
for (int i = 0; i < count; i += 1) {
|
282
|
+
for (int i = 0; i < result; i += 1) {
|
263
283
|
VALUE fiber = (VALUE)io_uring_cqe_get_data(cqes[i]);
|
264
284
|
VALUE result = INT2NUM(cqes[i]->res);
|
265
|
-
|
285
|
+
|
286
|
+
// fprintf(stderr, "cqes[i]->res = %d\n", cqes[i]->res);
|
287
|
+
|
266
288
|
io_uring_cqe_seen(data->ring, cqes[i]);
|
267
289
|
|
268
290
|
rb_funcall(fiber, id_transfer, 1, result);
|
269
291
|
}
|
270
292
|
|
271
|
-
return INT2NUM(
|
293
|
+
return INT2NUM(result);
|
272
294
|
}
|
273
295
|
|
274
296
|
void Init_Event_Backend_URing(VALUE Event_Backend) {
|
data/lib/event/backend/select.rb
CHANGED
@@ -43,20 +43,20 @@ module Event
|
|
43
43
|
def select(duration = nil)
|
44
44
|
readable, writable, _ = IO.select(@readable.keys, @writable.keys, nil, duration)
|
45
45
|
|
46
|
-
ready =
|
46
|
+
ready = Hash.new(0)
|
47
47
|
|
48
48
|
readable&.each do |io|
|
49
49
|
fiber = @readable.delete(io)
|
50
|
-
ready[fiber]
|
50
|
+
ready[fiber] |= READABLE
|
51
51
|
end
|
52
52
|
|
53
53
|
writable&.each do |io|
|
54
54
|
fiber = @writable.delete(io)
|
55
|
-
ready[fiber]
|
55
|
+
ready[fiber] |= WRITABLE
|
56
56
|
end
|
57
57
|
|
58
|
-
ready.
|
59
|
-
fiber.transfer
|
58
|
+
ready.each do |fiber, events|
|
59
|
+
fiber.transfer(events)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
data/lib/event/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
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-04-
|
11
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bake
|
@@ -73,6 +73,7 @@ extensions:
|
|
73
73
|
- ext/event/extconf.rb
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
|
+
- ext/event/backend/backend.h
|
76
77
|
- ext/event/backend/epoll.c
|
77
78
|
- ext/event/backend/epoll.h
|
78
79
|
- ext/event/backend/kqueue.c
|