event 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ #pragma once
22
+
23
+ #include <ruby.h>
24
+
25
+ #define EVENT_BACKEND_KQUEUE
26
+
27
+ void Init_Event_Backend_KQueue(VALUE Event_Backend);
@@ -0,0 +1,421 @@
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
+ #include "uring.h"
22
+ #include "backend.h"
23
+
24
+ #include <liburing.h>
25
+ #include <poll.h>
26
+ #include <time.h>
27
+
28
+ static VALUE Event_Backend_URing = Qnil;
29
+ static ID id_fileno, id_transfer;
30
+
31
+ enum {URING_ENTRIES = 128};
32
+ enum {URING_MAX_EVENTS = 128};
33
+
34
+ struct Event_Backend_URing {
35
+ VALUE loop;
36
+ struct io_uring ring;
37
+ };
38
+
39
+ void Event_Backend_URing_Type_mark(void *_data)
40
+ {
41
+ struct Event_Backend_URing *data = _data;
42
+ rb_gc_mark(data->loop);
43
+ }
44
+
45
+ static
46
+ void close_internal(struct Event_Backend_URing *data) {
47
+ if (data->ring.ring_fd >= 0) {
48
+ io_uring_queue_exit(&data->ring);
49
+ data->ring.ring_fd = -1;
50
+ }
51
+ }
52
+
53
+ void Event_Backend_URing_Type_free(void *_data)
54
+ {
55
+ struct Event_Backend_URing *data = _data;
56
+
57
+ close_internal(data);
58
+
59
+ free(data);
60
+ }
61
+
62
+ size_t Event_Backend_URing_Type_size(const void *data)
63
+ {
64
+ return sizeof(struct Event_Backend_URing);
65
+ }
66
+
67
+ static const rb_data_type_t Event_Backend_URing_Type = {
68
+ .wrap_struct_name = "Event::Backend::URing",
69
+ .function = {
70
+ .dmark = Event_Backend_URing_Type_mark,
71
+ .dfree = Event_Backend_URing_Type_free,
72
+ .dsize = Event_Backend_URing_Type_size,
73
+ },
74
+ .data = NULL,
75
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
76
+ };
77
+
78
+ VALUE Event_Backend_URing_allocate(VALUE self) {
79
+ struct Event_Backend_URing *data = NULL;
80
+ VALUE instance = TypedData_Make_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
81
+
82
+ data->loop = Qnil;
83
+ data->ring.ring_fd = -1;
84
+
85
+ return instance;
86
+ }
87
+
88
+ VALUE Event_Backend_URing_initialize(VALUE self, VALUE loop) {
89
+ struct Event_Backend_URing *data = NULL;
90
+ TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
91
+
92
+ data->loop = loop;
93
+
94
+ int result = io_uring_queue_init(URING_ENTRIES, &data->ring, 0);
95
+
96
+ if (result < 0) {
97
+ rb_syserr_fail(-result, "io_uring_queue_init");
98
+ }
99
+
100
+ rb_update_max_fd(data->ring.ring_fd);
101
+
102
+ return self;
103
+ }
104
+
105
+ VALUE Event_Backend_URing_close(VALUE self) {
106
+ struct Event_Backend_URing *data = NULL;
107
+ TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
108
+
109
+ close_internal(data);
110
+
111
+ return Qnil;
112
+ }
113
+
114
+ static inline
115
+ short poll_flags_from_events(int events) {
116
+ short flags = 0;
117
+
118
+ if (events & READABLE) flags |= POLLIN;
119
+ if (events & PRIORITY) flags |= POLLPRI;
120
+ if (events & WRITABLE) flags |= POLLOUT;
121
+
122
+ flags |= POLLERR;
123
+ flags |= POLLHUP;
124
+
125
+ return flags;
126
+ }
127
+
128
+ static inline
129
+ int events_from_poll_flags(short flags) {
130
+ int events = 0;
131
+
132
+ if (flags & POLLIN) events |= READABLE;
133
+ if (flags & POLLPRI) events |= PRIORITY;
134
+ if (flags & POLLOUT) events |= WRITABLE;
135
+
136
+ return events;
137
+ }
138
+
139
+ struct io_wait_arguments {
140
+ struct Event_Backend_URing *data;
141
+ VALUE fiber;
142
+ short flags;
143
+ };
144
+
145
+ struct io_uring_sqe * io_get_sqe(struct Event_Backend_URing *data) {
146
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&data->ring);
147
+
148
+ while (sqe == NULL) {
149
+ sqe = io_uring_get_sqe(&data->ring);
150
+ }
151
+
152
+ return sqe;
153
+ }
154
+
155
+ static
156
+ VALUE io_wait_rescue(VALUE _arguments, VALUE exception) {
157
+ struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
158
+ struct Event_Backend_URing *data = arguments->data;
159
+
160
+ struct io_uring_sqe *sqe = io_get_sqe(data);
161
+
162
+ // fprintf(stderr, "poll_remove(%p, %p)\n", sqe, (void*)arguments->fiber);
163
+
164
+ io_uring_prep_poll_remove(sqe, (void*)arguments->fiber);
165
+ io_uring_submit(&data->ring);
166
+
167
+ rb_exc_raise(exception);
168
+ };
169
+
170
+ static
171
+ VALUE io_wait_transfer(VALUE _arguments) {
172
+ struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
173
+ struct Event_Backend_URing *data = arguments->data;
174
+
175
+ VALUE result = rb_funcall(data->loop, id_transfer, 0);
176
+
177
+ // We explicitly filter the resulting events based on the requested events.
178
+ // In some cases, poll will report events we didn't ask for.
179
+ short flags = arguments->flags & NUM2INT(result);
180
+
181
+ return INT2NUM(events_from_poll_flags(flags));
182
+ };
183
+
184
+ VALUE Event_Backend_URing_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE events) {
185
+ struct Event_Backend_URing *data = NULL;
186
+ TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
187
+
188
+ int descriptor = NUM2INT(rb_funcall(io, id_fileno, 0));
189
+ struct io_uring_sqe *sqe = io_get_sqe(data);
190
+
191
+ if (!sqe) return INT2NUM(0);
192
+
193
+ short flags = poll_flags_from_events(NUM2INT(events));
194
+
195
+ // fprintf(stderr, "poll_add(%p, %d, %d, %p)\n", sqe, descriptor, flags, (void*)fiber);
196
+
197
+ io_uring_prep_poll_add(sqe, descriptor, flags);
198
+ io_uring_sqe_set_data(sqe, (void*)fiber);
199
+ io_uring_submit(&data->ring);
200
+
201
+ struct io_wait_arguments io_wait_arguments = {
202
+ .data = data,
203
+ .fiber = fiber,
204
+ .flags = flags
205
+ };
206
+
207
+ return rb_rescue(io_wait_transfer, (VALUE)&io_wait_arguments, io_wait_rescue, (VALUE)&io_wait_arguments);
208
+ }
209
+
210
+ inline static
211
+ void resize_to_capacity(VALUE string, size_t offset, size_t length) {
212
+ size_t current_length = RSTRING_LEN(string);
213
+ long difference = (long)(offset + length) - (long)current_length;
214
+
215
+ difference += 1;
216
+
217
+ if (difference > 0) {
218
+ rb_str_modify_expand(string, difference);
219
+ } else {
220
+ rb_str_modify(string);
221
+ }
222
+ }
223
+
224
+ inline static
225
+ void resize_to_fit(VALUE string, size_t offset, size_t length) {
226
+ size_t current_length = RSTRING_LEN(string);
227
+
228
+ if (current_length < (offset + length)) {
229
+ rb_str_set_len(string, offset + length);
230
+ }
231
+ }
232
+
233
+ VALUE Event_Backend_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
234
+ struct Event_Backend_URing *data = NULL;
235
+ TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
236
+
237
+ resize_to_capacity(buffer, NUM2SIZET(offset), NUM2SIZET(length));
238
+
239
+ int descriptor = NUM2INT(rb_funcall(io, id_fileno, 0));
240
+ struct io_uring_sqe *sqe = io_get_sqe(data);
241
+
242
+ struct iovec iovecs[1];
243
+ iovecs[0].iov_base = RSTRING_PTR(buffer) + NUM2SIZET(offset);
244
+ iovecs[0].iov_len = NUM2SIZET(length);
245
+
246
+ io_uring_prep_readv(sqe, descriptor, iovecs, 1, 0);
247
+ io_uring_sqe_set_data(sqe, (void*)fiber);
248
+ io_uring_submit(&data->ring);
249
+
250
+ // fprintf(stderr, "prep_readv(%p, %d, %ld)\n", sqe, descriptor, iovecs[0].iov_len);
251
+
252
+ int result = NUM2INT(rb_funcall(data->loop, id_transfer, 0));
253
+
254
+ if (result < 0) {
255
+ rb_syserr_fail(-result, strerror(-result));
256
+ }
257
+
258
+ resize_to_fit(buffer, NUM2SIZET(offset), (size_t)result);
259
+
260
+ return INT2NUM(result);
261
+ }
262
+
263
+ VALUE Event_Backend_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
264
+ struct Event_Backend_URing *data = NULL;
265
+ TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
266
+
267
+ if ((size_t)RSTRING_LEN(buffer) < NUM2SIZET(offset) + NUM2SIZET(length)) {
268
+ rb_raise(rb_eRuntimeError, "invalid offset/length exceeds bounds of buffer");
269
+ }
270
+
271
+ int descriptor = NUM2INT(rb_funcall(io, id_fileno, 0));
272
+ struct io_uring_sqe *sqe = io_get_sqe(data);
273
+
274
+ struct iovec iovecs[1];
275
+ iovecs[0].iov_base = RSTRING_PTR(buffer) + NUM2SIZET(offset);
276
+ iovecs[0].iov_len = NUM2SIZET(length);
277
+
278
+ io_uring_prep_writev(sqe, descriptor, iovecs, 1, 0);
279
+ io_uring_sqe_set_data(sqe, (void*)fiber);
280
+ io_uring_submit(&data->ring);
281
+
282
+ // fprintf(stderr, "prep_writev(%p, %d, %ld)\n", sqe, descriptor, iovecs[0].iov_len);
283
+
284
+ int result = NUM2INT(rb_funcall(data->loop, id_transfer, 0));
285
+
286
+ if (result < 0) {
287
+ rb_syserr_fail(-result, strerror(-result));
288
+ }
289
+
290
+ return INT2NUM(result);
291
+ }
292
+
293
+ static
294
+ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec *storage) {
295
+ if (duration == Qnil) {
296
+ return NULL;
297
+ }
298
+
299
+ if (FIXNUM_P(duration)) {
300
+ storage->tv_sec = NUM2TIMET(duration);
301
+ storage->tv_nsec = 0;
302
+
303
+ return storage;
304
+ }
305
+
306
+ else if (RB_FLOAT_TYPE_P(duration)) {
307
+ double value = RFLOAT_VALUE(duration);
308
+ time_t seconds = value;
309
+
310
+ storage->tv_sec = seconds;
311
+ storage->tv_nsec = (value - seconds) * 1000000000L;
312
+
313
+ return storage;
314
+ }
315
+
316
+ rb_raise(rb_eRuntimeError, "unable to convert timeout");
317
+ }
318
+
319
+ static
320
+ int timeout_nonblocking(struct __kernel_timespec *timespec) {
321
+ return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0;
322
+ }
323
+
324
+ struct select_arguments {
325
+ struct Event_Backend_URing *data;
326
+
327
+ int count;
328
+ struct io_uring_cqe **cqes;
329
+
330
+ struct __kernel_timespec storage;
331
+ struct __kernel_timespec *timeout;
332
+ };
333
+
334
+ static
335
+ void * select_internal(void *_arguments) {
336
+ struct select_arguments * arguments = (struct select_arguments *)_arguments;
337
+
338
+ arguments->count = io_uring_wait_cqes(&arguments->data->ring, arguments->cqes, 1, arguments->timeout, NULL);
339
+
340
+ // If waiting resulted in a timeout, there are 0 events.
341
+ if (arguments->count == -ETIME) {
342
+ arguments->count = 0;
343
+ }
344
+
345
+ return NULL;
346
+ }
347
+
348
+ static
349
+ int select_internal_without_gvl(struct select_arguments *arguments) {
350
+ rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0);
351
+
352
+ if (arguments->count < 0) {
353
+ rb_syserr_fail(-arguments->count, "select_internal_without_gvl:io_uring_wait_cqes");
354
+ }
355
+
356
+ return arguments->count;
357
+ }
358
+
359
+ VALUE Event_Backend_URing_select(VALUE self, VALUE duration) {
360
+ struct Event_Backend_URing *data = NULL;
361
+ TypedData_Get_Struct(self, struct Event_Backend_URing, &Event_Backend_URing_Type, data);
362
+
363
+ struct io_uring_cqe *cqes[URING_MAX_EVENTS];
364
+
365
+ // This is a non-blocking operation:
366
+ int result = io_uring_peek_batch_cqe(&data->ring, cqes, URING_MAX_EVENTS);
367
+
368
+ if (result < 0) {
369
+ rb_syserr_fail(-result, strerror(-result));
370
+ } else if (result == 0) {
371
+ // We might need to wait for events:
372
+ struct select_arguments arguments = {
373
+ .data = data,
374
+ .cqes = cqes,
375
+ .timeout = NULL,
376
+ };
377
+
378
+ arguments.timeout = make_timeout(duration, &arguments.storage);
379
+
380
+ if (!timeout_nonblocking(arguments.timeout)) {
381
+ result = select_internal_without_gvl(&arguments);
382
+ }
383
+ }
384
+
385
+ // fprintf(stderr, "cqes count=%d\n", result);
386
+
387
+ for (int i = 0; i < result; i += 1) {
388
+ // If the operation was cancelled, or the operation has no user data (fiber):
389
+ if (cqes[i]->res == -ECANCELED || cqes[i]->user_data == 0) {
390
+ continue;
391
+ }
392
+
393
+ VALUE fiber = (VALUE)io_uring_cqe_get_data(cqes[i]);
394
+ VALUE result = INT2NUM(cqes[i]->res);
395
+
396
+ // fprintf(stderr, "cqes[i] res=%d user_data=%p\n", cqes[i]->res, (void*)cqes[i]->user_data);
397
+
398
+ io_uring_cqe_seen(&data->ring, cqes[i]);
399
+
400
+ rb_funcall(fiber, id_transfer, 1, result);
401
+ }
402
+
403
+ return INT2NUM(result);
404
+ }
405
+
406
+ void Init_Event_Backend_URing(VALUE Event_Backend) {
407
+ id_fileno = rb_intern("fileno");
408
+ id_transfer = rb_intern("transfer");
409
+
410
+ Event_Backend_URing = rb_define_class_under(Event_Backend, "URing", rb_cObject);
411
+
412
+ rb_define_alloc_func(Event_Backend_URing, Event_Backend_URing_allocate);
413
+ rb_define_method(Event_Backend_URing, "initialize", Event_Backend_URing_initialize, 1);
414
+ rb_define_method(Event_Backend_URing, "close", Event_Backend_URing_close, 0);
415
+
416
+ rb_define_method(Event_Backend_URing, "io_wait", Event_Backend_URing_io_wait, 3);
417
+ rb_define_method(Event_Backend_URing, "select", Event_Backend_URing_select, 1);
418
+
419
+ rb_define_method(Event_Backend_URing, "io_read", Event_Backend_URing_io_read, 5);
420
+ rb_define_method(Event_Backend_URing, "io_write", Event_Backend_URing_io_write, 5);
421
+ }
@@ -0,0 +1,28 @@
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 <ruby.h>
24
+
25
+ #define EVENT_BACKEND_URING
26
+
27
+ void Init_Event_Backend_URing(VALUE Event_Backend);
28
+ VALUE Event_Backend_URing_select(VALUE self, VALUE duration);