polyphony 0.45.5 → 0.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +4 -4
  9. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -2
  10. data/ext/liburing/liburing.h +585 -0
  11. data/ext/liburing/liburing/README.md +4 -0
  12. data/ext/liburing/liburing/barrier.h +73 -0
  13. data/ext/liburing/liburing/compat.h +15 -0
  14. data/ext/liburing/liburing/io_uring.h +343 -0
  15. data/ext/liburing/queue.c +333 -0
  16. data/ext/liburing/register.c +187 -0
  17. data/ext/liburing/setup.c +210 -0
  18. data/ext/liburing/syscall.c +54 -0
  19. data/ext/liburing/syscall.h +18 -0
  20. data/ext/polyphony/backend.h +0 -14
  21. data/ext/polyphony/backend_common.h +109 -0
  22. data/ext/polyphony/backend_io_uring.c +884 -0
  23. data/ext/polyphony/backend_io_uring_context.c +73 -0
  24. data/ext/polyphony/backend_io_uring_context.h +52 -0
  25. data/ext/polyphony/{libev_backend.c → backend_libev.c} +202 -294
  26. data/ext/polyphony/event.c +1 -1
  27. data/ext/polyphony/extconf.rb +31 -13
  28. data/ext/polyphony/fiber.c +29 -22
  29. data/ext/polyphony/libev.c +4 -0
  30. data/ext/polyphony/libev.h +8 -2
  31. data/ext/polyphony/liburing.c +8 -0
  32. data/ext/polyphony/playground.c +51 -0
  33. data/ext/polyphony/polyphony.c +5 -5
  34. data/ext/polyphony/polyphony.h +16 -12
  35. data/ext/polyphony/polyphony_ext.c +10 -4
  36. data/ext/polyphony/queue.c +1 -1
  37. data/ext/polyphony/thread.c +11 -9
  38. data/lib/polyphony/adapters/trace.rb +2 -2
  39. data/lib/polyphony/core/global_api.rb +1 -4
  40. data/lib/polyphony/extensions/debug.rb +13 -0
  41. data/lib/polyphony/extensions/fiber.rb +2 -2
  42. data/lib/polyphony/extensions/socket.rb +59 -10
  43. data/lib/polyphony/version.rb +1 -1
  44. data/test/helper.rb +36 -4
  45. data/test/io_uring_test.rb +55 -0
  46. data/test/stress.rb +5 -2
  47. data/test/test_backend.rb +4 -6
  48. data/test/test_ext.rb +1 -2
  49. data/test/test_fiber.rb +22 -16
  50. data/test/test_global_api.rb +33 -35
  51. data/test/test_throttler.rb +3 -6
  52. data/test/test_trace.rb +7 -5
  53. metadata +22 -3
@@ -0,0 +1,18 @@
1
+ /* SPDX-License-Identifier: MIT */
2
+ #ifndef LIBURING_SYSCALL_H
3
+ #define LIBURING_SYSCALL_H
4
+
5
+ #include <signal.h>
6
+
7
+ struct io_uring_params;
8
+
9
+ /*
10
+ * System calls
11
+ */
12
+ extern int __sys_io_uring_setup(unsigned entries, struct io_uring_params *p);
13
+ extern int __sys_io_uring_enter(int fd, unsigned to_submit,
14
+ unsigned min_complete, unsigned flags, sigset_t *sig);
15
+ extern int __sys_io_uring_register(int fd, unsigned int opcode, const void *arg,
16
+ unsigned int nr_args);
17
+
18
+ #endif
@@ -3,20 +3,6 @@
3
3
 
4
4
  #include "ruby.h"
5
5
 
6
- // backend interface function signatures
7
-
8
- // VALUE LibevBackend_accept(VALUE self, VALUE sock);
9
- // VALUE LibevBackend_accept_loop(VALUE self, VALUE sock);
10
- // VALUE LibevBackend_connect(VALUE self, VALUE sock, VALUE host, VALUE port);
11
- // VALUE LibevBackend_finalize(VALUE self);
12
- // VALUE LibevBackend_post_fork(VALUE self);
13
- // VALUE LibevBackend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof);
14
- // VALUE LibevBackend_read_loop(VALUE self, VALUE io);
15
- // VALUE LibevBackend_sleep(VALUE self, VALUE duration);
16
- // VALUE LibevBackend_wait_io(VALUE self, VALUE io, VALUE write);
17
- // VALUE LibevBackend_wait_pid(VALUE self, VALUE pid);
18
- // VALUE LibevBackend_write(int argc, VALUE *argv, VALUE self);
19
-
20
6
  typedef VALUE (* backend_pending_count_t)(VALUE self);
21
7
  typedef VALUE (*backend_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
22
8
  typedef VALUE (* backend_ref_t)(VALUE self);
@@ -0,0 +1,109 @@
1
+ #include "ruby.h"
2
+ #include "ruby/io.h"
3
+
4
+ //////////////////////////////////////////////////////////////////////
5
+ //////////////////////////////////////////////////////////////////////
6
+ // the following is copied verbatim from the Ruby source code (io.c)
7
+ struct io_internal_read_struct {
8
+ int fd;
9
+ int nonblock;
10
+ void *buf;
11
+ size_t capa;
12
+ };
13
+
14
+ #define StringValue(v) rb_string_value(&(v))
15
+
16
+ inline int io_setstrbuf(VALUE *str, long len) {
17
+ #ifdef _WIN32
18
+ len = (len + 1) & ~1L; /* round up for wide char */
19
+ #endif
20
+ if (NIL_P(*str)) {
21
+ *str = rb_str_new(0, len);
22
+ return 1;
23
+ }
24
+ else {
25
+ VALUE s = StringValue(*str);
26
+ long clen = RSTRING_LEN(s);
27
+ if (clen >= len) {
28
+ rb_str_modify(s);
29
+ return 0;
30
+ }
31
+ len -= clen;
32
+ }
33
+ rb_str_modify_expand(*str, len);
34
+ return 0;
35
+ }
36
+
37
+ #define MAX_REALLOC_GAP 4096
38
+
39
+ inline void io_shrink_read_string(VALUE str, long n) {
40
+ if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
41
+ rb_str_resize(str, n);
42
+ }
43
+ }
44
+
45
+ inline void io_set_read_length(VALUE str, long n, int shrinkable) {
46
+ if (RSTRING_LEN(str) != n) {
47
+ rb_str_modify(str);
48
+ rb_str_set_len(str, n);
49
+ if (shrinkable) io_shrink_read_string(str, n);
50
+ }
51
+ }
52
+
53
+ inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
54
+ if (fptr->encs.enc) {
55
+ return fptr->encs.enc;
56
+ }
57
+ return rb_default_external_encoding();
58
+ }
59
+
60
+ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
61
+ OBJ_TAINT(str);
62
+ rb_enc_associate(str, io_read_encoding(fptr));
63
+ return str;
64
+ }
65
+
66
+ //////////////////////////////////////////////////////////////////////
67
+ //////////////////////////////////////////////////////////////////////
68
+
69
+ inline VALUE backend_await(Backend_t *backend) {
70
+ VALUE ret;
71
+ backend->ref_count++;
72
+ ret = Thread_switch_fiber(rb_thread_current());
73
+ backend->ref_count--;
74
+ RB_GC_GUARD(ret);
75
+ return ret;
76
+ }
77
+
78
+ inline VALUE backend_snooze() {
79
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
80
+ return Thread_switch_fiber(rb_thread_current());
81
+ }
82
+
83
+ // macros for doing read loops
84
+
85
+ #define READ_LOOP_PREPARE_STR() { \
86
+ str = Qnil; \
87
+ shrinkable = io_setstrbuf(&str, len); \
88
+ buf = RSTRING_PTR(str); \
89
+ total = 0; \
90
+ OBJ_TAINT(str); \
91
+ }
92
+
93
+ #define READ_LOOP_YIELD_STR() { \
94
+ io_set_read_length(str, total, shrinkable); \
95
+ io_enc_str(str, fptr); \
96
+ rb_yield(str); \
97
+ READ_LOOP_PREPARE_STR(); \
98
+ }
99
+
100
+ inline void rectify_io_file_pos(rb_io_t *fptr) {
101
+ // Apparently after reopening a closed file, the file position is not reset,
102
+ // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
103
+ // find out if that's the case.
104
+ // See: https://github.com/digital-fabric/polyphony/issues/30
105
+ if (fptr->rbuf.len > 0) {
106
+ lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
107
+ fptr->rbuf.len = 0;
108
+ }
109
+ }
@@ -0,0 +1,884 @@
1
+ #ifdef POLYPHONY_BACKEND_LIBURING
2
+
3
+ #include <netdb.h>
4
+ #include <sys/socket.h>
5
+ #include <sys/uio.h>
6
+ #include <unistd.h>
7
+ #include <fcntl.h>
8
+ #include <netinet/in.h>
9
+ #include <arpa/inet.h>
10
+
11
+ #include "polyphony.h"
12
+ #include "../liburing/liburing.h"
13
+ #include "ruby/thread.h"
14
+ #include "backend_io_uring_context.h"
15
+
16
+ #include <poll.h>
17
+ #include <sys/types.h>
18
+ #include <sys/eventfd.h>
19
+ #include <sys/wait.h>
20
+ #include <errno.h>
21
+
22
+ #ifndef __NR_pidfd_open
23
+ #define __NR_pidfd_open 434 /* System call # on most architectures */
24
+ #endif
25
+
26
+ static int pidfd_open(pid_t pid, unsigned int flags) {
27
+ return syscall(__NR_pidfd_open, pid, flags);
28
+ }
29
+
30
+ VALUE cTCPSocket;
31
+ VALUE SYM_io_uring;
32
+
33
+ typedef struct Backend_t {
34
+ struct io_uring ring;
35
+ op_context_store_t store;
36
+ int waiting_for_cqe;
37
+ unsigned int ref_count;
38
+ unsigned int run_no_wait_count;
39
+ unsigned int pending_sqes;
40
+ unsigned int prepared_limit;
41
+ int event_fd;
42
+ } Backend_t;
43
+
44
+ #include "backend_common.h"
45
+
46
+ static size_t Backend_size(const void *ptr) {
47
+ return sizeof(Backend_t);
48
+ }
49
+
50
+ static const rb_data_type_t Backend_type = {
51
+ "IOUringBackend",
52
+ {0, 0, Backend_size,},
53
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
54
+ };
55
+
56
+ static VALUE Backend_allocate(VALUE klass) {
57
+ Backend_t *backend = ALLOC(Backend_t);
58
+
59
+ return TypedData_Wrap_Struct(klass, &Backend_type, backend);
60
+ }
61
+
62
+ #define GetBackend(obj, backend) \
63
+ TypedData_Get_Struct((obj), Backend_t, &Backend_type, (backend))
64
+
65
+ static VALUE Backend_initialize(VALUE self) {
66
+ Backend_t *backend;
67
+ GetBackend(self, backend);
68
+
69
+ backend->waiting_for_cqe = 0;
70
+ backend->ref_count = 0;
71
+ backend->run_no_wait_count = 0;
72
+ backend->pending_sqes = 0;
73
+ backend->prepared_limit = 1024;
74
+
75
+ context_store_initialize(&backend->store);
76
+ io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
77
+ backend->event_fd = -1;
78
+
79
+ return Qnil;
80
+ }
81
+
82
+ VALUE Backend_finalize(VALUE self) {
83
+ Backend_t *backend;
84
+ GetBackend(self, backend);
85
+
86
+ io_uring_queue_exit(&backend->ring);
87
+ if (backend->event_fd != -1) close(backend->event_fd);
88
+ context_store_free(&backend->store);
89
+ return self;
90
+ }
91
+
92
+ VALUE Backend_post_fork(VALUE self) {
93
+ Backend_t *backend;
94
+ GetBackend(self, backend);
95
+
96
+ io_uring_queue_exit(&backend->ring);
97
+ io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
98
+ context_store_free(&backend->store);
99
+ backend->waiting_for_cqe = 0;
100
+ backend->ref_count = 0;
101
+ backend->run_no_wait_count = 0;
102
+ backend->pending_sqes = 0;
103
+
104
+ return self;
105
+ }
106
+
107
+ VALUE Backend_ref(VALUE self) {
108
+ Backend_t *backend;
109
+ GetBackend(self, backend);
110
+
111
+ backend->ref_count++;
112
+ return self;
113
+ }
114
+
115
+ VALUE Backend_unref(VALUE self) {
116
+ Backend_t *backend;
117
+ GetBackend(self, backend);
118
+
119
+ backend->ref_count--;
120
+ return self;
121
+ }
122
+
123
+ int Backend_ref_count(VALUE self) {
124
+ Backend_t *backend;
125
+ GetBackend(self, backend);
126
+
127
+ return backend->ref_count;
128
+ }
129
+
130
+ void Backend_reset_ref_count(VALUE self) {
131
+ Backend_t *backend;
132
+ GetBackend(self, backend);
133
+
134
+ backend->ref_count = 0;
135
+ }
136
+
137
+ VALUE Backend_pending_count(VALUE self) {
138
+ return INT2NUM(0);
139
+ }
140
+
141
+ typedef struct poll_context {
142
+ struct io_uring *ring;
143
+ struct io_uring_cqe *cqe;
144
+ int result;
145
+ } poll_context_t;
146
+
147
+ extern int __sys_io_uring_enter(int fd, unsigned to_submit, unsigned min_complete, unsigned flags, sigset_t *sig);
148
+
149
+ void *io_uring_backend_poll_without_gvl(void *ptr) {
150
+ poll_context_t *ctx = (poll_context_t *)ptr;
151
+ ctx->result = io_uring_wait_cqe(ctx->ring, &ctx->cqe);
152
+ return NULL;
153
+ }
154
+
155
+ // copied from queue.c
156
+ static inline bool cq_ring_needs_flush(struct io_uring *ring) {
157
+ return IO_URING_READ_ONCE(*ring->sq.kflags) & IORING_SQ_CQ_OVERFLOW;
158
+ }
159
+
160
+ void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *backend) {
161
+ op_context_t *ctx = io_uring_cqe_get_data(cqe);
162
+ if (ctx == 0) return;
163
+
164
+ ctx->result = cqe->res;
165
+
166
+ if (ctx->completed)
167
+ // already marked as deleted as result of fiber resuming before op
168
+ // completion, so we can release the context
169
+ context_store_release(&backend->store, ctx);
170
+ else {
171
+ // otherwise, we mark it as completed, schedule the fiber and let it deal
172
+ // with releasing the context
173
+ ctx->completed = 1;
174
+ if (ctx->result != -ECANCELED) Fiber_make_runnable(ctx->fiber, Qnil);
175
+ }
176
+ }
177
+
178
+ // adapted from io_uring_peek_batch_cqe in queue.c
179
+ // this peeks at cqes and for each one
180
+ void io_uring_backend_handle_ready_cqes(Backend_t *backend) {
181
+ struct io_uring *ring = &backend->ring;
182
+ bool overflow_checked = false;
183
+ struct io_uring_cqe *cqe;
184
+ unsigned head;
185
+ unsigned cqe_count;
186
+
187
+ again:
188
+ cqe_count = 0;
189
+ io_uring_for_each_cqe(ring, head, cqe) {
190
+ ++cqe_count;
191
+ io_uring_backend_handle_completion(cqe, backend);
192
+ }
193
+ io_uring_cq_advance(ring, cqe_count);
194
+
195
+ if (overflow_checked) goto done;
196
+
197
+ if (cq_ring_needs_flush(ring)) {
198
+ __sys_io_uring_enter(ring->ring_fd, 0, 0, IORING_ENTER_GETEVENTS, NULL);
199
+ overflow_checked = true;
200
+ goto again;
201
+ }
202
+
203
+ done:
204
+ return;
205
+ }
206
+
207
+ void io_uring_backend_poll(Backend_t *backend) {
208
+ poll_context_t poll_ctx;
209
+ poll_ctx.ring = &backend->ring;
210
+ if (backend->pending_sqes) {
211
+ backend->pending_sqes = 0;
212
+ io_uring_submit(&backend->ring);
213
+ }
214
+
215
+ backend->waiting_for_cqe = 1;
216
+ rb_thread_call_without_gvl(io_uring_backend_poll_without_gvl, (void *)&poll_ctx, RUBY_UBF_IO, 0);
217
+ backend->waiting_for_cqe = 0;
218
+ if (poll_ctx.result < 0) return;
219
+
220
+ io_uring_backend_handle_completion(poll_ctx.cqe, backend);
221
+ io_uring_cqe_seen(&backend->ring, poll_ctx.cqe);
222
+ }
223
+
224
+ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
225
+ int is_nowait = nowait == Qtrue;
226
+ Backend_t *backend;
227
+ GetBackend(self, backend);
228
+
229
+ if (is_nowait) {
230
+ backend->run_no_wait_count++;
231
+ if (backend->run_no_wait_count < 10) return self;
232
+
233
+ long runnable_count = Runqueue_len(runqueue);
234
+ if (backend->run_no_wait_count < runnable_count) return self;
235
+ }
236
+
237
+ backend->run_no_wait_count = 0;
238
+
239
+ if (is_nowait && backend->pending_sqes) {
240
+ backend->pending_sqes = 0;
241
+ io_uring_submit(&backend->ring);
242
+ }
243
+
244
+ COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
245
+ if (!is_nowait) io_uring_backend_poll(backend);
246
+ io_uring_backend_handle_ready_cqes(backend);
247
+ COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
248
+
249
+ return self;
250
+ }
251
+
252
+ VALUE Backend_wakeup(VALUE self) {
253
+ Backend_t *backend;
254
+ GetBackend(self, backend);
255
+
256
+ if (backend->waiting_for_cqe) {
257
+ // Since we're currently blocking while waiting for a completion, we add a
258
+ // NOP which would cause the io_uring_enter syscall to return
259
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
260
+ io_uring_prep_nop(sqe);
261
+ backend->pending_sqes = 0;
262
+ io_uring_submit(&backend->ring);
263
+
264
+ return Qtrue;
265
+ }
266
+
267
+ return Qnil;
268
+ }
269
+
270
+ inline void io_uring_backend_defer_submit(Backend_t *backend) {
271
+ backend->pending_sqes += 1;
272
+ if (backend->pending_sqes >= backend->prepared_limit) {
273
+ backend->pending_sqes = 0;
274
+ io_uring_submit(&backend->ring);
275
+ }
276
+ }
277
+
278
+ int io_uring_backend_defer_submit_and_await(
279
+ Backend_t *backend,
280
+ struct io_uring_sqe *sqe,
281
+ op_context_t *ctx,
282
+ VALUE *value_ptr
283
+ )
284
+ {
285
+ VALUE switchpoint_result = Qnil;
286
+
287
+ io_uring_sqe_set_data(sqe, ctx);
288
+ io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
289
+ io_uring_backend_defer_submit(backend);
290
+
291
+ backend->ref_count++;
292
+ switchpoint_result = backend_await(backend);
293
+ backend->ref_count--;
294
+
295
+ if (!ctx->completed) {
296
+ ctx->result = -ECANCELED;
297
+
298
+ // op was not completed, so we need to cancel it
299
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
300
+ io_uring_prep_cancel(sqe, ctx, 0);
301
+ backend->pending_sqes = 0;
302
+ io_uring_submit(&backend->ring);
303
+ }
304
+
305
+ if (value_ptr) (*value_ptr) = switchpoint_result;
306
+ RB_GC_GUARD(switchpoint_result);
307
+ return ctx->result;
308
+ }
309
+
310
+ VALUE io_uring_backend_wait_fd(Backend_t *backend, int fd, int write) {
311
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_POLL);
312
+ VALUE resumed_value = Qnil;
313
+
314
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
315
+ io_uring_prep_poll_add(sqe, fd, write ? POLLOUT : POLLIN);
316
+
317
+ io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resumed_value);
318
+ RB_GC_GUARD(resumed_value);
319
+ return resumed_value;
320
+ }
321
+
322
+ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
323
+ Backend_t *backend;
324
+ rb_io_t *fptr;
325
+ long dynamic_len = length == Qnil;
326
+ long len = dynamic_len ? 4096 : NUM2INT(length);
327
+ int shrinkable = io_setstrbuf(&str, len);
328
+ char *buf = RSTRING_PTR(str);
329
+ long total = 0;
330
+ int read_to_eof = RTEST(to_eof);
331
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
332
+
333
+ GetBackend(self, backend);
334
+ if (underlying_io != Qnil) io = underlying_io;
335
+ GetOpenFile(io, fptr);
336
+ rb_io_check_byte_readable(fptr);
337
+ rectify_io_file_pos(fptr);
338
+ OBJ_TAINT(str);
339
+
340
+ while (1) {
341
+ VALUE resume_value = Qnil;
342
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
343
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
344
+ io_uring_prep_read(sqe, fptr->fd, buf, len - total, -1);
345
+
346
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
347
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
348
+ RAISE_IF_EXCEPTION(resume_value);
349
+ if (!ctx->completed) return resume_value;
350
+ RB_GC_GUARD(resume_value);
351
+
352
+ if (result < 0)
353
+ rb_syserr_fail(-result, strerror(-result));
354
+ else if (result == 0)
355
+ break; // EOF
356
+ else {
357
+ total += result;
358
+ if (!read_to_eof) break;
359
+
360
+ if (total == len) {
361
+ if (!dynamic_len) break;
362
+
363
+ rb_str_resize(str, total);
364
+ rb_str_modify_expand(str, len);
365
+ buf = RSTRING_PTR(str) + total;
366
+ shrinkable = 0;
367
+ len += len;
368
+ }
369
+ else buf += result;
370
+ }
371
+ }
372
+
373
+ io_set_read_length(str, total, shrinkable);
374
+ io_enc_str(str, fptr);
375
+
376
+ if (total == 0) return Qnil;
377
+
378
+ return str;
379
+ }
380
+
381
+ VALUE Backend_read_loop(VALUE self, VALUE io) {
382
+ Backend_t *backend;
383
+ rb_io_t *fptr;
384
+ VALUE str;
385
+ long total;
386
+ long len = 8192;
387
+ int shrinkable;
388
+ char *buf;
389
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
390
+
391
+ READ_LOOP_PREPARE_STR();
392
+
393
+ GetBackend(self, backend);
394
+ if (underlying_io != Qnil) io = underlying_io;
395
+ GetOpenFile(io, fptr);
396
+ rb_io_check_byte_readable(fptr);
397
+ rectify_io_file_pos(fptr);
398
+
399
+ while (1) {
400
+ VALUE resume_value = Qnil;
401
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
402
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
403
+ io_uring_prep_read(sqe, fptr->fd, buf, len, -1);
404
+
405
+ ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
406
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
407
+ RAISE_IF_EXCEPTION(resume_value);
408
+ if (!ctx->completed) return resume_value;
409
+ RB_GC_GUARD(resume_value);
410
+
411
+ if (result < 0)
412
+ rb_syserr_fail(-result, strerror(-result));
413
+ else if (result == 0)
414
+ break; // EOF
415
+ else {
416
+ total = result;
417
+ READ_LOOP_YIELD_STR();
418
+ }
419
+ }
420
+
421
+ RB_GC_GUARD(str);
422
+
423
+ return io;
424
+ }
425
+
426
+ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
427
+ Backend_t *backend;
428
+ rb_io_t *fptr;
429
+ VALUE underlying_io;
430
+
431
+ underlying_io = rb_ivar_get(io, ID_ivar_io);
432
+ if (underlying_io != Qnil) io = underlying_io;
433
+ GetBackend(self, backend);
434
+ io = rb_io_get_write_io(io);
435
+ GetOpenFile(io, fptr);
436
+
437
+ char *buf = StringValuePtr(str);
438
+ long len = RSTRING_LEN(str);
439
+ long left = len;
440
+
441
+ while (left > 0) {
442
+ VALUE resume_value = Qnil;
443
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_WRITE);
444
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
445
+ io_uring_prep_write(sqe, fptr->fd, buf, left, -1);
446
+
447
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
448
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
449
+ RAISE_IF_EXCEPTION(resume_value);
450
+ if (!ctx->completed) return resume_value;
451
+ RB_GC_GUARD(resume_value);
452
+
453
+ if (result < 0)
454
+ rb_syserr_fail(-result, strerror(-result));
455
+ else {
456
+ buf += result;
457
+ left -= result;
458
+ }
459
+ }
460
+
461
+ return INT2NUM(len);
462
+ }
463
+
464
+ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
465
+ Backend_t *backend;
466
+ rb_io_t *fptr;
467
+ VALUE underlying_io;
468
+ long total_length = 0;
469
+ long total_written = 0;
470
+ struct iovec *iov = 0;
471
+ struct iovec *iov_ptr = 0;
472
+ int iov_count = argc;
473
+
474
+ underlying_io = rb_ivar_get(io, ID_ivar_io);
475
+ if (underlying_io != Qnil) io = underlying_io;
476
+ GetBackend(self, backend);
477
+ io = rb_io_get_write_io(io);
478
+ GetOpenFile(io, fptr);
479
+
480
+ iov = malloc(iov_count * sizeof(struct iovec));
481
+ for (int i = 0; i < argc; i++) {
482
+ VALUE str = argv[i];
483
+ iov[i].iov_base = StringValuePtr(str);
484
+ iov[i].iov_len = RSTRING_LEN(str);
485
+ total_length += iov[i].iov_len;
486
+ }
487
+ iov_ptr = iov;
488
+
489
+ while (1) {
490
+ VALUE resume_value = Qnil;
491
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_WRITEV);
492
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
493
+ io_uring_prep_writev(sqe, fptr->fd, iov_ptr, iov_count, -1);
494
+
495
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
496
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
497
+ if (TEST_EXCEPTION(resume_value)) {
498
+ free(iov);
499
+ RAISE_EXCEPTION(resume_value);
500
+ }
501
+ if (!ctx->completed) {
502
+ free(iov);
503
+ return resume_value;
504
+ }
505
+ RB_GC_GUARD(resume_value);
506
+
507
+ if (result < 0) {
508
+ free(iov);
509
+ rb_syserr_fail(-result, strerror(-result));
510
+ }
511
+ else {
512
+ total_written += result;
513
+ if (total_written >= total_length) break;
514
+
515
+ while (result > 0) {
516
+ if ((size_t) result < iov_ptr[0].iov_len) {
517
+ iov_ptr[0].iov_base = (char *) iov_ptr[0].iov_base + result;
518
+ iov_ptr[0].iov_len -= result;
519
+ result = 0;
520
+ }
521
+ else {
522
+ result -= iov_ptr[0].iov_len;
523
+ iov_ptr += 1;
524
+ iov_count -= 1;
525
+ }
526
+ }
527
+ }
528
+ }
529
+
530
+ free(iov);
531
+ return INT2NUM(total_written);
532
+ }
533
+
534
+ VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
535
+ if (argc < 2)
536
+ // TODO: raise ArgumentError
537
+ rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
538
+
539
+ return (argc == 2) ?
540
+ Backend_write(self, argv[0], argv[1]) :
541
+ Backend_writev(self, argv[0], argc - 1, argv + 1);
542
+ }
543
+
544
+ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
545
+ Backend_t *backend;
546
+ rb_io_t *fptr;
547
+ long dynamic_len = length == Qnil;
548
+ long len = dynamic_len ? 4096 : NUM2INT(length);
549
+ int shrinkable = io_setstrbuf(&str, len);
550
+ char *buf = RSTRING_PTR(str);
551
+ long total = 0;
552
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
553
+
554
+ GetBackend(self, backend);
555
+ if (underlying_io != Qnil) io = underlying_io;
556
+ GetOpenFile(io, fptr);
557
+ rb_io_check_byte_readable(fptr);
558
+ rectify_io_file_pos(fptr);
559
+ OBJ_TAINT(str);
560
+
561
+ while (1) {
562
+ VALUE resume_value = Qnil;
563
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_RECV);
564
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
565
+ io_uring_prep_recv(sqe, fptr->fd, buf, len - total, 0);
566
+
567
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
568
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
569
+ RAISE_IF_EXCEPTION(resume_value);
570
+ if (!ctx->completed) return resume_value;
571
+ RB_GC_GUARD(resume_value);
572
+
573
+ if (result < 0)
574
+ rb_syserr_fail(-result, strerror(-result));
575
+ else {
576
+ total += result;
577
+ break;
578
+ }
579
+ }
580
+
581
+ io_set_read_length(str, total, shrinkable);
582
+ io_enc_str(str, fptr);
583
+
584
+ if (total == 0) return Qnil;
585
+
586
+ return str;
587
+ }
588
+
589
+ VALUE Backend_recv_loop(VALUE self, VALUE io) {
590
+ Backend_t *backend;
591
+ rb_io_t *fptr;
592
+ VALUE str;
593
+ long total;
594
+ long len = 8192;
595
+ int shrinkable;
596
+ char *buf;
597
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
598
+
599
+ READ_LOOP_PREPARE_STR();
600
+
601
+ GetBackend(self, backend);
602
+ if (underlying_io != Qnil) io = underlying_io;
603
+ GetOpenFile(io, fptr);
604
+ rb_io_check_byte_readable(fptr);
605
+ rectify_io_file_pos(fptr);
606
+
607
+ while (1) {
608
+ VALUE resume_value = Qnil;
609
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_RECV);
610
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
611
+ io_uring_prep_recv(sqe, fptr->fd, buf, len, 0);
612
+
613
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
614
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
615
+ RAISE_IF_EXCEPTION(resume_value);
616
+ if (!ctx->completed) return resume_value;
617
+ RB_GC_GUARD(resume_value);
618
+
619
+ if (result < 0)
620
+ rb_syserr_fail(-result, strerror(-result));
621
+ else if (result == 0)
622
+ break; // EOF
623
+ else {
624
+ total = result;
625
+ READ_LOOP_YIELD_STR();
626
+ }
627
+ }
628
+
629
+ RB_GC_GUARD(str);
630
+ return io;
631
+ }
632
+
633
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
634
+ Backend_t *backend;
635
+ rb_io_t *fptr;
636
+ VALUE underlying_io;
637
+
638
+ underlying_io = rb_ivar_get(io, ID_ivar_io);
639
+ if (underlying_io != Qnil) io = underlying_io;
640
+ GetBackend(self, backend);
641
+ io = rb_io_get_write_io(io);
642
+ GetOpenFile(io, fptr);
643
+
644
+ char *buf = StringValuePtr(str);
645
+ long len = RSTRING_LEN(str);
646
+ long left = len;
647
+
648
+ while (left > 0) {
649
+ VALUE resume_value = Qnil;
650
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SEND);
651
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
652
+ io_uring_prep_send(sqe, fptr->fd, buf, left, 0);
653
+
654
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
655
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
656
+ RAISE_IF_EXCEPTION(resume_value);
657
+ if (!ctx->completed) return resume_value;
658
+ RB_GC_GUARD(resume_value);
659
+
660
+ if (result < 0)
661
+ rb_syserr_fail(-result, strerror(-result));
662
+ else {
663
+ buf += result;
664
+ left -= result;
665
+ }
666
+ }
667
+
668
+ return INT2NUM(len);
669
+ }
670
+
671
+ VALUE io_uring_backend_accept(Backend_t *backend, VALUE sock, int loop) {
672
+ rb_io_t *fptr;
673
+ struct sockaddr addr;
674
+ socklen_t len = (socklen_t)sizeof addr;
675
+ VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
676
+ VALUE socket = Qnil;
677
+ if (underlying_sock != Qnil) sock = underlying_sock;
678
+
679
+ GetOpenFile(sock, fptr);
680
+ while (1) {
681
+ VALUE resume_value = Qnil;
682
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
683
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
684
+ io_uring_prep_accept(sqe, fptr->fd, &addr, &len, 0);
685
+
686
+ int fd = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
687
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
688
+ RAISE_IF_EXCEPTION(resume_value);
689
+ if (!ctx->completed) return resume_value;
690
+ RB_GC_GUARD(resume_value);
691
+
692
+ if (fd < 0)
693
+ rb_syserr_fail(-fd, strerror(-fd));
694
+ else {
695
+ rb_io_t *fp;
696
+
697
+ socket = rb_obj_alloc(cTCPSocket);
698
+ MakeOpenFile(socket, fp);
699
+ rb_update_max_fd(fd);
700
+ fp->fd = fd;
701
+ fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
702
+ rb_io_ascii8bit_binmode(socket);
703
+ rb_io_synchronized(fp);
704
+
705
+ // if (rsock_do_not_reverse_lookup) {
706
+ // fp->mode |= FMODE_NOREVLOOKUP;
707
+ // }
708
+ if (loop) {
709
+ rb_yield(socket);
710
+ socket = Qnil;
711
+ }
712
+ else
713
+ return socket;
714
+ }
715
+ }
716
+ RB_GC_GUARD(socket);
717
+ return Qnil;
718
+ }
719
+
720
+ VALUE Backend_accept(VALUE self, VALUE sock) {
721
+ Backend_t *backend;
722
+ GetBackend(self, backend);
723
+ return io_uring_backend_accept(backend, sock, 0);
724
+ }
725
+
726
+ VALUE Backend_accept_loop(VALUE self, VALUE sock) {
727
+ Backend_t *backend;
728
+ GetBackend(self, backend);
729
+ io_uring_backend_accept(backend, sock, 1);
730
+ return self;
731
+ }
732
+
733
+ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
734
+ Backend_t *backend;
735
+ rb_io_t *fptr;
736
+ struct sockaddr_in addr;
737
+ char *host_buf = StringValueCStr(host);
738
+ VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
739
+ if (underlying_sock != Qnil) sock = underlying_sock;
740
+
741
+ GetBackend(self, backend);
742
+ GetOpenFile(sock, fptr);
743
+
744
+ addr.sin_family = AF_INET;
745
+ addr.sin_addr.s_addr = inet_addr(host_buf);
746
+ addr.sin_port = htons(NUM2INT(port));
747
+
748
+ VALUE resume_value = Qnil;
749
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_CONNECT);
750
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
751
+ io_uring_prep_connect(sqe, fptr->fd, (struct sockaddr *)&addr, sizeof(addr));
752
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
753
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
754
+ RAISE_IF_EXCEPTION(resume_value);
755
+ if (!ctx->completed) return resume_value;
756
+ RB_GC_GUARD(resume_value);
757
+
758
+ if (result < 0) rb_syserr_fail(-result, strerror(-result));
759
+ return sock;
760
+ }
761
+
762
+ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
763
+ Backend_t *backend;
764
+ rb_io_t *fptr;
765
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
766
+ if (underlying_io != Qnil) io = underlying_io;
767
+ GetBackend(self, backend);
768
+ GetOpenFile(io, fptr);
769
+
770
+ VALUE resume_value = io_uring_backend_wait_fd(backend, fptr->fd, RTEST(write));
771
+ RAISE_IF_EXCEPTION(resume_value);
772
+ RB_GC_GUARD(resume_value);
773
+ return self;
774
+ }
775
+
776
+ VALUE Backend_sleep(VALUE self, VALUE duration) {
777
+ Backend_t *backend;
778
+ struct io_uring_sqe *sqe;
779
+ double duration_integral;
780
+ double duration_fraction = modf(NUM2DBL(duration), &duration_integral);
781
+ struct __kernel_timespec ts;
782
+
783
+ GetBackend(self, backend);
784
+ sqe = io_uring_get_sqe(&backend->ring);
785
+ ts.tv_sec = duration_integral;
786
+ ts.tv_nsec = floor(duration_fraction * 1000000000);
787
+
788
+ VALUE resume_value = Qnil;
789
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_TIMEOUT);
790
+ io_uring_prep_timeout(sqe, &ts, 0, 0);
791
+
792
+ io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
793
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
794
+ RAISE_IF_EXCEPTION(resume_value);
795
+ RB_GC_GUARD(resume_value);
796
+ return resume_value;
797
+ }
798
+
799
+ VALUE Backend_waitpid(VALUE self, VALUE pid) {
800
+ Backend_t *backend;
801
+ int pid_int = NUM2INT(pid);
802
+ int fd = pidfd_open(pid_int, 0);
803
+ GetBackend(self, backend);
804
+
805
+ VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
806
+ close(fd);
807
+
808
+ RAISE_IF_EXCEPTION(resume_value);
809
+ RB_GC_GUARD(resume_value);
810
+
811
+ int status;
812
+ pid_t ret = waitpid(pid_int, &status, WNOHANG);
813
+ return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
814
+ }
815
+
816
+ VALUE Backend_wait_event(VALUE self, VALUE raise) {
817
+ Backend_t *backend;
818
+ GetBackend(self, backend);
819
+
820
+ if (backend->event_fd == -1) {
821
+ backend->event_fd = eventfd(0, 0);
822
+ if (backend->event_fd == -1) {
823
+ int n = errno;
824
+ rb_syserr_fail(n, strerror(n));
825
+ }
826
+ }
827
+
828
+ VALUE resume_value = io_uring_backend_wait_fd(backend, backend->event_fd, 0);
829
+ if (RTEST(raise)) RAISE_IF_EXCEPTION(resume_value);
830
+ RB_GC_GUARD(resume_value);
831
+ return resume_value;
832
+ }
833
+
834
+ VALUE Backend_kind(VALUE self) {
835
+ return SYM_io_uring;
836
+ }
837
+
838
+ void Init_Backend() {
839
+ rb_require("socket");
840
+ cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
841
+
842
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
843
+ rb_define_alloc_func(cBackend, Backend_allocate);
844
+
845
+ rb_define_method(cBackend, "initialize", Backend_initialize, 0);
846
+ rb_define_method(cBackend, "finalize", Backend_finalize, 0);
847
+ rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
848
+ rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
849
+
850
+ rb_define_method(cBackend, "ref", Backend_ref, 0);
851
+ rb_define_method(cBackend, "unref", Backend_unref, 0);
852
+
853
+ rb_define_method(cBackend, "poll", Backend_poll, 3);
854
+ rb_define_method(cBackend, "break", Backend_wakeup, 0);
855
+
856
+ rb_define_method(cBackend, "read", Backend_read, 4);
857
+ rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
858
+ rb_define_method(cBackend, "write", Backend_write_m, -1);
859
+ rb_define_method(cBackend, "recv", Backend_recv, 3);
860
+ rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
861
+ rb_define_method(cBackend, "send", Backend_send, 2);
862
+ rb_define_method(cBackend, "accept", Backend_accept, 1);
863
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 1);
864
+ rb_define_method(cBackend, "connect", Backend_connect, 3);
865
+ rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
866
+ rb_define_method(cBackend, "sleep", Backend_sleep, 1);
867
+ rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
868
+ rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
869
+
870
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
871
+
872
+ SYM_io_uring = ID2SYM(rb_intern("io_uring"));
873
+
874
+ __BACKEND__.pending_count = Backend_pending_count;
875
+ __BACKEND__.poll = Backend_poll;
876
+ __BACKEND__.ref = Backend_ref;
877
+ __BACKEND__.ref_count = Backend_ref_count;
878
+ __BACKEND__.reset_ref_count = Backend_reset_ref_count;
879
+ __BACKEND__.unref = Backend_unref;
880
+ __BACKEND__.wait_event = Backend_wait_event;
881
+ __BACKEND__.wakeup = Backend_wakeup;
882
+ }
883
+
884
+ #endif // POLYPHONY_BACKEND_LIBURING