polyphony 0.49.2 → 0.53.0

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +34 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +37 -3
  6. data/examples/core/forking.rb +2 -2
  7. data/examples/core/nested.rb +21 -0
  8. data/examples/core/suspend.rb +13 -0
  9. data/examples/core/terminate_main_fiber.rb +12 -0
  10. data/examples/io/echo_server.rb +1 -0
  11. data/examples/io/tcp_proxy.rb +2 -2
  12. data/ext/polyphony/backend_common.h +58 -8
  13. data/ext/polyphony/backend_io_uring.c +223 -41
  14. data/ext/polyphony/backend_io_uring_context.c +1 -0
  15. data/ext/polyphony/backend_io_uring_context.h +1 -0
  16. data/ext/polyphony/backend_libev.c +322 -34
  17. data/ext/polyphony/event.c +1 -1
  18. data/ext/polyphony/extconf.rb +9 -2
  19. data/ext/polyphony/fiber.c +2 -1
  20. data/ext/polyphony/polyphony.c +102 -0
  21. data/ext/polyphony/polyphony.h +33 -2
  22. data/ext/polyphony/polyphony_ext.c +3 -0
  23. data/ext/polyphony/queue.c +1 -1
  24. data/ext/polyphony/runqueue.c +7 -1
  25. data/ext/polyphony/runqueue_ring_buffer.c +9 -0
  26. data/ext/polyphony/runqueue_ring_buffer.h +1 -0
  27. data/ext/polyphony/socket_extensions.c +33 -0
  28. data/ext/polyphony/thread.c +14 -0
  29. data/lib/polyphony/adapters/irb.rb +1 -1
  30. data/lib/polyphony/adapters/mysql2.rb +1 -1
  31. data/lib/polyphony/adapters/postgres.rb +5 -5
  32. data/lib/polyphony/adapters/process.rb +4 -4
  33. data/lib/polyphony/core/exceptions.rb +1 -0
  34. data/lib/polyphony/core/global_api.rb +6 -6
  35. data/lib/polyphony/core/sync.rb +1 -1
  36. data/lib/polyphony/core/throttler.rb +1 -1
  37. data/lib/polyphony/core/timer.rb +63 -20
  38. data/lib/polyphony/extensions/core.rb +5 -5
  39. data/lib/polyphony/extensions/fiber.rb +2 -0
  40. data/lib/polyphony/extensions/io.rb +21 -22
  41. data/lib/polyphony/extensions/openssl.rb +6 -6
  42. data/lib/polyphony/extensions/socket.rb +56 -47
  43. data/lib/polyphony/version.rb +1 -1
  44. data/polyphony.gemspec +6 -5
  45. data/test/helper.rb +1 -1
  46. data/test/stress.rb +2 -0
  47. data/test/test_backend.rb +69 -5
  48. data/test/test_fiber.rb +16 -0
  49. data/test/test_global_api.rb +2 -2
  50. data/test/test_io.rb +84 -1
  51. data/test/test_kernel.rb +1 -1
  52. data/test/test_signal.rb +1 -1
  53. data/test/test_socket.rb +61 -0
  54. data/test/test_timer.rb +41 -8
  55. metadata +22 -60
@@ -16,18 +16,36 @@
16
16
 
17
17
  #include "polyphony.h"
18
18
  #include "../liburing/liburing.h"
19
- #include "ruby/thread.h"
20
19
  #include "backend_io_uring_context.h"
20
+ #include "ruby/thread.h"
21
+ #include "ruby/io.h"
21
22
 
22
- #ifndef __NR_pidfd_open
23
- #define __NR_pidfd_open 434 /* System call # on most architectures */
24
- #endif
23
+ VALUE SYM_io_uring;
25
24
 
26
- static int pidfd_open(pid_t pid, unsigned int flags) {
27
- return syscall(__NR_pidfd_open, pid, flags);
25
+ #ifdef POLYPHONY_UNSET_NONBLOCK
26
+ ID ID_ivar_is_nonblocking;
27
+
28
+ // One of the changes introduced in Ruby 3.0 as part of the work on the
29
+ // FiberScheduler interface is that all created sockets are marked as
30
+ // non-blocking. This prevents the io_uring backend from working correctly,
31
+ // since it will return an EAGAIN error just like a normal syscall. So here
32
+ // instead of setting O_NONBLOCK (which is required for the libev backend), we
33
+ // unset it.
34
+ inline void io_unset_nonblock(rb_io_t *fptr, VALUE io) {
35
+ VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
36
+ if (is_nonblocking == Qfalse) return;
37
+
38
+ rb_ivar_set(io, ID_ivar_is_nonblocking, Qfalse);
39
+
40
+ int oflags = fcntl(fptr->fd, F_GETFL);
41
+ if ((oflags == -1) && (oflags & O_NONBLOCK)) return;
42
+ oflags &= !O_NONBLOCK;
43
+ fcntl(fptr->fd, F_SETFL, oflags);
28
44
  }
29
-
30
- VALUE SYM_io_uring;
45
+ #else
46
+ // NOP
47
+ #define io_unset_nonblock(fptr, io)
48
+ #endif
31
49
 
32
50
  typedef struct Backend_t {
33
51
  // common fields
@@ -260,7 +278,7 @@ int io_uring_backend_defer_submit_and_await(
260
278
  VALUE switchpoint_result = Qnil;
261
279
 
262
280
  io_uring_sqe_set_data(sqe, ctx);
263
- // io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
281
+ io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
264
282
  io_uring_backend_defer_submit(backend);
265
283
 
266
284
  switchpoint_result = backend_await(backend);
@@ -308,6 +326,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
308
326
  if (underlying_io != Qnil) io = underlying_io;
309
327
  GetOpenFile(io, fptr);
310
328
  rb_io_check_byte_readable(fptr);
329
+ io_unset_nonblock(fptr, io);
311
330
  rectify_io_file_pos(fptr);
312
331
  OBJ_TAINT(str);
313
332
 
@@ -368,6 +387,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
368
387
  if (underlying_io != Qnil) io = underlying_io;
369
388
  GetOpenFile(io, fptr);
370
389
  rb_io_check_byte_readable(fptr);
390
+ io_unset_nonblock(fptr, io);
371
391
  rectify_io_file_pos(fptr);
372
392
 
373
393
  while (1) {
@@ -397,6 +417,53 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
397
417
  return io;
398
418
  }
399
419
 
420
+ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
421
+ Backend_t *backend;
422
+ rb_io_t *fptr;
423
+ VALUE str;
424
+ long total;
425
+ long len = 8192;
426
+ int shrinkable;
427
+ char *buf;
428
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
429
+ ID method_id = SYM2ID(method);
430
+
431
+ READ_LOOP_PREPARE_STR();
432
+
433
+ GetBackend(self, backend);
434
+ if (underlying_io != Qnil) io = underlying_io;
435
+ GetOpenFile(io, fptr);
436
+ rb_io_check_byte_readable(fptr);
437
+ io_unset_nonblock(fptr, io);
438
+ rectify_io_file_pos(fptr);
439
+
440
+ while (1) {
441
+ VALUE resume_value = Qnil;
442
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
443
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
444
+ io_uring_prep_read(sqe, fptr->fd, buf, len, -1);
445
+
446
+ ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
447
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
448
+ RAISE_IF_EXCEPTION(resume_value);
449
+ if (!ctx->completed) return resume_value;
450
+ RB_GC_GUARD(resume_value);
451
+
452
+ if (result < 0)
453
+ rb_syserr_fail(-result, strerror(-result));
454
+ else if (!result)
455
+ break; // EOF
456
+ else {
457
+ total = result;
458
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
459
+ }
460
+ }
461
+
462
+ RB_GC_GUARD(str);
463
+
464
+ return io;
465
+ }
466
+
400
467
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
401
468
  Backend_t *backend;
402
469
  rb_io_t *fptr;
@@ -407,6 +474,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
407
474
  GetBackend(self, backend);
408
475
  io = rb_io_get_write_io(io);
409
476
  GetOpenFile(io, fptr);
477
+ io_unset_nonblock(fptr, io);
410
478
 
411
479
  char *buf = StringValuePtr(str);
412
480
  long len = RSTRING_LEN(str);
@@ -450,6 +518,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
450
518
  GetBackend(self, backend);
451
519
  io = rb_io_get_write_io(io);
452
520
  GetOpenFile(io, fptr);
521
+ io_unset_nonblock(fptr, io);
453
522
 
454
523
  iov = malloc(iov_count * sizeof(struct iovec));
455
524
  for (int i = 0; i < argc; i++) {
@@ -529,6 +598,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
529
598
  if (underlying_io != Qnil) io = underlying_io;
530
599
  GetOpenFile(io, fptr);
531
600
  rb_io_check_byte_readable(fptr);
601
+ io_unset_nonblock(fptr, io);
532
602
  rectify_io_file_pos(fptr);
533
603
  OBJ_TAINT(str);
534
604
 
@@ -576,6 +646,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
576
646
  if (underlying_io != Qnil) io = underlying_io;
577
647
  GetOpenFile(io, fptr);
578
648
  rb_io_check_byte_readable(fptr);
649
+ io_unset_nonblock(fptr, io);
579
650
  rectify_io_file_pos(fptr);
580
651
 
581
652
  while (1) {
@@ -604,7 +675,53 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
604
675
  return io;
605
676
  }
606
677
 
607
- VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
678
+ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
679
+ Backend_t *backend;
680
+ rb_io_t *fptr;
681
+ VALUE str;
682
+ long total;
683
+ long len = 8192;
684
+ int shrinkable;
685
+ char *buf;
686
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
687
+ ID method_id = SYM2ID(method);
688
+
689
+ READ_LOOP_PREPARE_STR();
690
+
691
+ GetBackend(self, backend);
692
+ if (underlying_io != Qnil) io = underlying_io;
693
+ GetOpenFile(io, fptr);
694
+ rb_io_check_byte_readable(fptr);
695
+ io_unset_nonblock(fptr, io);
696
+ rectify_io_file_pos(fptr);
697
+
698
+ while (1) {
699
+ VALUE resume_value = Qnil;
700
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_RECV);
701
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
702
+ io_uring_prep_recv(sqe, fptr->fd, buf, len, 0);
703
+
704
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
705
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
706
+ RAISE_IF_EXCEPTION(resume_value);
707
+ if (!ctx->completed) return resume_value;
708
+ RB_GC_GUARD(resume_value);
709
+
710
+ if (result < 0)
711
+ rb_syserr_fail(-result, strerror(-result));
712
+ else if (!result)
713
+ break; // EOF
714
+ else {
715
+ total = result;
716
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
717
+ }
718
+ }
719
+
720
+ RB_GC_GUARD(str);
721
+ return io;
722
+ }
723
+
724
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
608
725
  Backend_t *backend;
609
726
  rb_io_t *fptr;
610
727
  VALUE underlying_io;
@@ -614,16 +731,18 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
614
731
  GetBackend(self, backend);
615
732
  io = rb_io_get_write_io(io);
616
733
  GetOpenFile(io, fptr);
734
+ io_unset_nonblock(fptr, io);
617
735
 
618
736
  char *buf = StringValuePtr(str);
619
737
  long len = RSTRING_LEN(str);
620
738
  long left = len;
739
+ int flags_int = NUM2INT(flags);
621
740
 
622
741
  while (left > 0) {
623
742
  VALUE resume_value = Qnil;
624
743
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SEND);
625
744
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
626
- io_uring_prep_send(sqe, fptr->fd, buf, left, 0);
745
+ io_uring_prep_send(sqe, fptr->fd, buf, left, flags_int);
627
746
 
628
747
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
629
748
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -651,6 +770,8 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE soc
651
770
  if (underlying_sock != Qnil) server_socket = underlying_sock;
652
771
 
653
772
  GetOpenFile(server_socket, fptr);
773
+ io_unset_nonblock(fptr, server_socket);
774
+
654
775
  while (1) {
655
776
  VALUE resume_value = Qnil;
656
777
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
@@ -704,6 +825,60 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
704
825
  return self;
705
826
  }
706
827
 
828
+ VALUE io_uring_backend_splice(Backend_t *backend, VALUE src, VALUE dest, VALUE maxlen, int loop) {
829
+ rb_io_t *src_fptr;
830
+ rb_io_t *dest_fptr;
831
+ VALUE underlying_io;
832
+ int total = 0;
833
+
834
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
835
+ if (underlying_io != Qnil) src = underlying_io;
836
+ GetOpenFile(src, src_fptr);
837
+ io_unset_nonblock(src_fptr, src);
838
+
839
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
840
+ if (underlying_io != Qnil) dest = underlying_io;
841
+ dest = rb_io_get_write_io(dest);
842
+ GetOpenFile(dest, dest_fptr);
843
+ io_unset_nonblock(dest_fptr, dest);
844
+
845
+ VALUE resume_value = Qnil;
846
+
847
+ while (1) {
848
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SPLICE);
849
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
850
+ io_uring_prep_splice(sqe, src_fptr->fd, -1, dest_fptr->fd, -1, NUM2INT(maxlen), 0);
851
+
852
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
853
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
854
+ RAISE_IF_EXCEPTION(resume_value);
855
+ if (!ctx->completed) return resume_value;
856
+
857
+ if (result < 0)
858
+ rb_syserr_fail(-result, strerror(-result));
859
+
860
+ if (result == 0 || !loop) return INT2NUM(total);
861
+ total += result;
862
+ }
863
+
864
+ RB_GC_GUARD(resume_value);
865
+ }
866
+
867
+ VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
868
+ Backend_t *backend;
869
+ GetBackend(self, backend);
870
+
871
+ return io_uring_backend_splice(backend, src, dest, maxlen, 0);
872
+ }
873
+
874
+ VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE chunksize) {
875
+ Backend_t *backend;
876
+ GetBackend(self, backend);
877
+
878
+ return io_uring_backend_splice(backend, src, dest, chunksize, 1);
879
+ }
880
+
881
+
707
882
  VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
708
883
  Backend_t *backend;
709
884
  rb_io_t *fptr;
@@ -714,6 +889,7 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
714
889
 
715
890
  GetBackend(self, backend);
716
891
  GetOpenFile(sock, fptr);
892
+ io_unset_nonblock(fptr, sock);
717
893
 
718
894
  addr.sin_family = AF_INET;
719
895
  addr.sin_addr.s_addr = inet_addr(host_buf);
@@ -740,6 +916,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
740
916
  if (underlying_io != Qnil) io = underlying_io;
741
917
  GetBackend(self, backend);
742
918
  GetOpenFile(io, fptr);
919
+ io_unset_nonblock(fptr, io);
743
920
 
744
921
  VALUE resume_value = io_uring_backend_wait_fd(backend, fptr->fd, RTEST(write));
745
922
  RAISE_IF_EXCEPTION(resume_value);
@@ -811,18 +988,6 @@ VALUE Backend_timer_loop(VALUE self, VALUE interval) {
811
988
  }
812
989
  }
813
990
 
814
- VALUE Backend_timeout_safe(VALUE arg) {
815
- return rb_yield(arg);
816
- }
817
-
818
- VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
819
- return exception;
820
- }
821
-
822
- VALUE Backend_timeout_ensure_safe(VALUE arg) {
823
- return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
824
- }
825
-
826
991
  struct Backend_timeout_ctx {
827
992
  Backend_t *backend;
828
993
  op_context_t *ctx;
@@ -861,7 +1026,6 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
861
1026
  ctx->resume_value = timeout;
862
1027
  io_uring_prep_timeout(sqe, &ts, 0, 0);
863
1028
  io_uring_sqe_set_data(sqe, ctx);
864
- io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
865
1029
  io_uring_backend_defer_submit(backend);
866
1030
 
867
1031
  struct Backend_timeout_ctx timeout_ctx = {backend, ctx};
@@ -879,19 +1043,28 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
879
1043
  }
880
1044
 
881
1045
  VALUE Backend_waitpid(VALUE self, VALUE pid) {
882
- Backend_t *backend;
883
1046
  int pid_int = NUM2INT(pid);
884
1047
  int fd = pidfd_open(pid_int, 0);
885
- GetBackend(self, backend);
886
-
887
- VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
888
- close(fd);
889
1048
 
890
- RAISE_IF_EXCEPTION(resume_value);
891
- RB_GC_GUARD(resume_value);
1049
+ if (fd >= 0) {
1050
+ Backend_t *backend;
1051
+ GetBackend(self, backend);
892
1052
 
1053
+ VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
1054
+ close(fd);
1055
+ RAISE_IF_EXCEPTION(resume_value);
1056
+ RB_GC_GUARD(resume_value);
1057
+ }
1058
+
893
1059
  int status;
894
1060
  pid_t ret = waitpid(pid_int, &status, WNOHANG);
1061
+ if (ret < 0) {
1062
+ int e = errno;
1063
+ if (e == ECHILD)
1064
+ ret = pid_int;
1065
+ else
1066
+ rb_syserr_fail(e, strerror(e));
1067
+ }
895
1068
  return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
896
1069
  }
897
1070
 
@@ -918,7 +1091,7 @@ VALUE Backend_kind(VALUE self) {
918
1091
  }
919
1092
 
920
1093
  void Init_Backend() {
921
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1094
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
922
1095
  rb_define_alloc_func(cBackend, Backend_allocate);
923
1096
 
924
1097
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -927,24 +1100,33 @@ void Init_Backend() {
927
1100
 
928
1101
  rb_define_method(cBackend, "poll", Backend_poll, 3);
929
1102
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1103
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
930
1104
 
1105
+ rb_define_method(cBackend, "accept", Backend_accept, 2);
1106
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
1107
+ rb_define_method(cBackend, "connect", Backend_connect, 3);
1108
+ rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
931
1109
  rb_define_method(cBackend, "read", Backend_read, 4);
932
1110
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
933
- rb_define_method(cBackend, "write", Backend_write_m, -1);
934
1111
  rb_define_method(cBackend, "recv", Backend_recv, 3);
1112
+ rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
935
1113
  rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
936
- rb_define_method(cBackend, "send", Backend_send, 2);
937
- rb_define_method(cBackend, "accept", Backend_accept, 2);
938
- rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
939
- rb_define_method(cBackend, "connect", Backend_connect, 3);
940
- rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1114
+ rb_define_method(cBackend, "send", Backend_send, 3);
1115
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
941
1116
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
942
- rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
1117
+ rb_define_method(cBackend, "splice", Backend_splice, 3);
1118
+ rb_define_method(cBackend, "splice_to_eof", Backend_splice_to_eof, 3);
943
1119
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
944
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1120
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
945
1121
  rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
1122
+ rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1123
+ rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1124
+ rb_define_method(cBackend, "write", Backend_write_m, -1);
946
1125
 
947
- rb_define_method(cBackend, "kind", Backend_kind, 0);
1126
+
1127
+ #ifdef POLYPHONY_UNSET_NONBLOCK
1128
+ ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
1129
+ #endif
948
1130
 
949
1131
  SYM_io_uring = ID2SYM(rb_intern("io_uring"));
950
1132
  }
@@ -10,6 +10,7 @@ const char *op_type_to_str(enum op_type type) {
10
10
  case OP_WRITE: return "WRITE";
11
11
  case OP_RECV: return "RECV";
12
12
  case OP_SEND: return "SEND";
13
+ case OP_SPLICE: return "SPLICE";
13
14
  case OP_TIMEOUT: return "TIMEOUT";
14
15
  case OP_POLL: return "POLL";
15
16
  case OP_ACCEPT: return "ACCEPT";
@@ -10,6 +10,7 @@ enum op_type {
10
10
  OP_WRITE,
11
11
  OP_RECV,
12
12
  OP_SEND,
13
+ OP_SPLICE,
13
14
  OP_TIMEOUT,
14
15
  OP_POLL,
15
16
  OP_ACCEPT,
@@ -1,13 +1,57 @@
1
+ /*
2
+ # Libev-based blocking ops backend for Polyphony
3
+
4
+ ## Backend initialization
5
+
6
+ The backend is initialized by creating an event loop. For the main thread the
7
+ default event loop is used, but we since we don't need to handle any signals
8
+ (see the waitpid implementation below) we might as well use a non-default event
9
+ loop for the main thread at some time in the future.
10
+
11
+ In addition, we create an async watcher that is used for interrupting the #poll
12
+ method from another thread.
13
+
14
+ ## Blocking operations
15
+
16
+ I/O operations start by making sure the io has been set to non-blocking
17
+ operation (O_NONBLOCK). That way, if the syscall would block, we'd get an
18
+ EWOULDBLOCK or EAGAIN instead of blocking.
19
+
20
+ Once the OS has indicated that the operation would block, we start a watcher
21
+ (its type corresponding to the desired operation), and call ev_xxxx_start. in We
22
+ then call Thread_switch_fiber and switch to another fiber while waiting for the
23
+ watcher to be triggered.
24
+
25
+ ## Polling for events
26
+
27
+ Backend_poll is called either once the corresponding thread has no more work to
28
+ do (no runnable fibers) or periodically while the thread is scheduling fibers in
29
+ order to prevent event starvation.
30
+
31
+ ## Behaviour of waitpid
32
+
33
+ On Linux 5.3+, pidfd_open will be used, otherwise a libev child watcher will be
34
+ used. Note that if a child watcher is used, waitpid will only work from the main
35
+ thread.
36
+
37
+ */
38
+
1
39
  #ifdef POLYPHONY_BACKEND_LIBEV
2
40
 
41
+ #ifdef POLYPHONY_LINUX
42
+ #define _GNU_SOURCE 1
43
+ #endif
44
+
45
+ #include <fcntl.h>
3
46
  #include <netdb.h>
4
47
  #include <sys/socket.h>
5
48
  #include <sys/uio.h>
6
49
  #include <unistd.h>
7
- #include <fcntl.h>
8
50
  #include <netinet/in.h>
9
51
  #include <arpa/inet.h>
10
52
  #include <stdnoreturn.h>
53
+ #include <sys/types.h>
54
+ #include <sys/wait.h>
11
55
 
12
56
  #include "polyphony.h"
13
57
  #include "../libev/ev.h"
@@ -74,17 +118,27 @@ void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, in
74
118
  // of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
75
119
  }
76
120
 
121
+ inline struct ev_loop *libev_new_loop() {
122
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
123
+ return ev_loop_new(EVFLAG_NOSIGMASK);
124
+ #else
125
+ int is_main_thread = (rb_thread_current() == rb_thread_main());
126
+ return is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
127
+ #endif
128
+ }
129
+
77
130
  static VALUE Backend_initialize(VALUE self) {
78
131
  Backend_t *backend;
79
- VALUE thread = rb_thread_current();
80
- int is_main_thread = (thread == rb_thread_main());
81
-
132
+
82
133
  GetBackend(self, backend);
83
- backend->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
134
+ backend->ev_loop = libev_new_loop();
84
135
 
136
+ // start async watcher used for breaking a poll op (from another thread)
85
137
  ev_async_init(&backend->break_async, break_async_callback);
86
138
  ev_async_start(backend->ev_loop, &backend->break_async);
87
- ev_unref(backend->ev_loop); // don't count the break_async watcher
139
+ // the break_async watcher is unreferenced, in order for Backend_poll to not
140
+ // block when no other watcher is active
141
+ ev_unref(backend->ev_loop);
88
142
 
89
143
  backend->currently_polling = 0;
90
144
  backend->pending_count = 0;
@@ -332,6 +386,58 @@ error:
332
386
  return RAISE_EXCEPTION(switchpoint_result);
333
387
  }
334
388
 
389
+ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
390
+ Backend_t *backend;
391
+ struct libev_io watcher;
392
+ rb_io_t *fptr;
393
+ VALUE str;
394
+ long total;
395
+ long len = 8192;
396
+ int shrinkable;
397
+ char *buf;
398
+ VALUE switchpoint_result = Qnil;
399
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
400
+ ID method_id = SYM2ID(method);
401
+
402
+ READ_LOOP_PREPARE_STR();
403
+
404
+ GetBackend(self, backend);
405
+ if (underlying_io != Qnil) io = underlying_io;
406
+ GetOpenFile(io, fptr);
407
+ rb_io_check_byte_readable(fptr);
408
+ io_set_nonblock(fptr, io);
409
+ rectify_io_file_pos(fptr);
410
+ watcher.fiber = Qnil;
411
+
412
+ while (1) {
413
+ ssize_t n = read(fptr->fd, buf, len);
414
+ if (n < 0) {
415
+ int e = errno;
416
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
417
+
418
+ switchpoint_result = libev_wait_fd_with_watcher(backend, fptr->fd, &watcher, EV_READ);
419
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
420
+ }
421
+ else {
422
+ switchpoint_result = backend_snooze();
423
+
424
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
425
+
426
+ if (n == 0) break; // EOF
427
+ total = n;
428
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
429
+ }
430
+ }
431
+
432
+ RB_GC_GUARD(str);
433
+ RB_GC_GUARD(watcher.fiber);
434
+ RB_GC_GUARD(switchpoint_result);
435
+
436
+ return io;
437
+ error:
438
+ return RAISE_EXCEPTION(switchpoint_result);
439
+ }
440
+
335
441
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
336
442
  Backend_t *backend;
337
443
  struct libev_io watcher;
@@ -618,6 +724,173 @@ error:
618
724
  return RAISE_EXCEPTION(switchpoint_result);
619
725
  }
620
726
 
727
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
728
+ Backend_t *backend;
729
+ struct libev_io watcher;
730
+ rb_io_t *fptr;
731
+ VALUE switchpoint_result = Qnil;
732
+ VALUE underlying_io;
733
+ char *buf = StringValuePtr(str);
734
+ long len = RSTRING_LEN(str);
735
+ long left = len;
736
+ int flags_int = NUM2INT(flags);
737
+
738
+ underlying_io = rb_ivar_get(io, ID_ivar_io);
739
+ if (underlying_io != Qnil) io = underlying_io;
740
+ GetBackend(self, backend);
741
+ io = rb_io_get_write_io(io);
742
+ GetOpenFile(io, fptr);
743
+ io_set_nonblock(fptr, io);
744
+ watcher.fiber = Qnil;
745
+
746
+ while (left > 0) {
747
+ ssize_t n = send(fptr->fd, buf, left, flags_int);
748
+ if (n < 0) {
749
+ int e = errno;
750
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
751
+
752
+ switchpoint_result = libev_wait_fd_with_watcher(backend, fptr->fd, &watcher, EV_WRITE);
753
+
754
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
755
+ }
756
+ else {
757
+ buf += n;
758
+ left -= n;
759
+ }
760
+ }
761
+
762
+ if (watcher.fiber == Qnil) {
763
+ switchpoint_result = backend_snooze();
764
+
765
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
766
+ }
767
+
768
+ RB_GC_GUARD(watcher.fiber);
769
+ RB_GC_GUARD(switchpoint_result);
770
+
771
+ return INT2NUM(len);
772
+ error:
773
+ return RAISE_EXCEPTION(switchpoint_result);
774
+ }
775
+
776
+ VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
777
+ Backend_t *backend;
778
+ struct libev_io watcher;
779
+ VALUE switchpoint_result = Qnil;
780
+ VALUE underlying_io;
781
+ rb_io_t *src_fptr;
782
+ rb_io_t *dest_fptr;
783
+ int len;
784
+
785
+ #ifndef POLYPHONY_LINUX
786
+ rb_raise(rb_eRuntimeError, "splice not supported");
787
+ #endif
788
+
789
+ GetBackend(self, backend);
790
+
791
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
792
+ if (underlying_io != Qnil) src = underlying_io;
793
+ GetOpenFile(src, src_fptr);
794
+ io_set_nonblock(src_fptr, src);
795
+
796
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
797
+ if (underlying_io != Qnil) dest = underlying_io;
798
+ dest = rb_io_get_write_io(dest);
799
+ GetOpenFile(dest, dest_fptr);
800
+ io_set_nonblock(dest_fptr, dest);
801
+
802
+ watcher.fiber = Qnil;
803
+ while (1) {
804
+ len = splice(src_fptr->fd, 0, dest_fptr->fd, 0, NUM2INT(maxlen), 0);
805
+ if (len < 0) {
806
+ int e = errno;
807
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
808
+
809
+ switchpoint_result = libev_wait_fd_with_watcher(backend, src_fptr->fd, &watcher, EV_READ);
810
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
811
+
812
+ switchpoint_result = libev_wait_fd_with_watcher(backend, dest_fptr->fd, &watcher, EV_WRITE);
813
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
814
+ }
815
+ else {
816
+ break;
817
+ }
818
+ }
819
+
820
+ if (watcher.fiber == Qnil) {
821
+ switchpoint_result = backend_snooze();
822
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
823
+ }
824
+
825
+ RB_GC_GUARD(watcher.fiber);
826
+ RB_GC_GUARD(switchpoint_result);
827
+
828
+ return INT2NUM(len);
829
+ error:
830
+ return RAISE_EXCEPTION(switchpoint_result);
831
+ }
832
+
833
+ VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
834
+ Backend_t *backend;
835
+ struct libev_io watcher;
836
+ VALUE switchpoint_result = Qnil;
837
+ VALUE underlying_io;
838
+ rb_io_t *src_fptr;
839
+ rb_io_t *dest_fptr;
840
+ int len;
841
+ int total = 0;
842
+
843
+ #ifndef POLYPHONY_LINUX
844
+ rb_raise(rb_eRuntimeError, "splice not supported");
845
+ #endif
846
+
847
+ GetBackend(self, backend);
848
+
849
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
850
+ if (underlying_io != Qnil) src = underlying_io;
851
+ GetOpenFile(src, src_fptr);
852
+ io_set_nonblock(src_fptr, src);
853
+
854
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
855
+ if (underlying_io != Qnil) dest = underlying_io;
856
+ dest = rb_io_get_write_io(dest);
857
+ GetOpenFile(dest, dest_fptr);
858
+ io_set_nonblock(dest_fptr, dest);
859
+
860
+ watcher.fiber = Qnil;
861
+ while (1) {
862
+ len = splice(src_fptr->fd, 0, dest_fptr->fd, 0, NUM2INT(maxlen), 0);
863
+ if (len < 0) {
864
+ int e = errno;
865
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
866
+
867
+ switchpoint_result = libev_wait_fd_with_watcher(backend, src_fptr->fd, &watcher, EV_READ);
868
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
869
+
870
+ switchpoint_result = libev_wait_fd_with_watcher(backend, dest_fptr->fd, &watcher, EV_WRITE);
871
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
872
+ }
873
+ else if (len == 0) {
874
+ break;
875
+ }
876
+ else {
877
+ total += len;
878
+ }
879
+ }
880
+
881
+ if (watcher.fiber == Qnil) {
882
+ switchpoint_result = backend_snooze();
883
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
884
+ }
885
+
886
+ RB_GC_GUARD(watcher.fiber);
887
+ RB_GC_GUARD(switchpoint_result);
888
+
889
+ return INT2NUM(total);
890
+ error:
891
+ return RAISE_EXCEPTION(switchpoint_result);
892
+ }
893
+
621
894
  VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
622
895
  Backend_t *backend;
623
896
  rb_io_t *fptr;
@@ -685,26 +958,12 @@ noreturn VALUE Backend_timer_loop(VALUE self, VALUE interval) {
685
958
  RB_GC_GUARD(switchpoint_result);
686
959
 
687
960
  rb_yield(Qnil);
688
-
689
- while (1) {
961
+ do {
690
962
  next_time += interval_d;
691
- if (next_time > now) break;
692
- }
963
+ } while (next_time <= now);
693
964
  }
694
965
  }
695
966
 
696
- VALUE Backend_timeout_safe(VALUE arg) {
697
- return rb_yield(arg);
698
- }
699
-
700
- VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
701
- return exception;
702
- }
703
-
704
- VALUE Backend_timeout_ensure_safe(VALUE arg) {
705
- return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
706
- }
707
-
708
967
  struct libev_timeout {
709
968
  struct ev_timer timer;
710
969
  VALUE fiber;
@@ -759,13 +1018,39 @@ VALUE Backend_timeout(int argc,VALUE *argv, VALUE self) {
759
1018
  return result;
760
1019
  }
761
1020
 
1021
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
1022
+ VALUE Backend_waitpid(VALUE self, VALUE pid) {
1023
+ int pid_int = NUM2INT(pid);
1024
+ int fd = pidfd_open(pid_int, 0);
1025
+ if (fd >= 0) {
1026
+ Backend_t *backend;
1027
+ GetBackend(self, backend);
1028
+
1029
+ VALUE resume_value = libev_wait_fd(backend, fd, EV_READ, 0);
1030
+ close(fd);
1031
+ RAISE_IF_EXCEPTION(resume_value);
1032
+ RB_GC_GUARD(resume_value);
1033
+ }
1034
+ else {
1035
+ int e = errno;
1036
+ rb_syserr_fail(e, strerror(e));
1037
+ }
1038
+
1039
+ int status = 0;
1040
+ pid_t ret = waitpid(pid_int, &status, WNOHANG);
1041
+ if (ret < 0) {
1042
+ int e = errno;
1043
+ rb_syserr_fail(e, strerror(e));
1044
+ }
1045
+ return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
1046
+ }
1047
+ #else
762
1048
  struct libev_child {
763
1049
  struct ev_child child;
764
1050
  VALUE fiber;
765
1051
  };
766
1052
 
767
- void Backend_child_callback(EV_P_ ev_child *w, int revents)
768
- {
1053
+ void Backend_child_callback(EV_P_ ev_child *w, int revents) {
769
1054
  struct libev_child *watcher = (struct libev_child *)w;
770
1055
  int exit_status = WEXITSTATUS(w->rstatus);
771
1056
  VALUE status;
@@ -792,6 +1077,7 @@ VALUE Backend_waitpid(VALUE self, VALUE pid) {
792
1077
  RB_GC_GUARD(switchpoint_result);
793
1078
  return switchpoint_result;
794
1079
  }
1080
+ #endif
795
1081
 
796
1082
  void Backend_async_callback(EV_P_ ev_async *w, int revents) { }
797
1083
 
@@ -820,7 +1106,7 @@ VALUE Backend_kind(VALUE self) {
820
1106
  void Init_Backend() {
821
1107
  ev_set_allocator(xrealloc);
822
1108
 
823
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1109
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
824
1110
  rb_define_alloc_func(cBackend, Backend_allocate);
825
1111
 
826
1112
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -829,24 +1115,26 @@ void Init_Backend() {
829
1115
 
830
1116
  rb_define_method(cBackend, "poll", Backend_poll, 3);
831
1117
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1118
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
832
1119
 
833
- rb_define_method(cBackend, "read", Backend_read, 4);
834
- rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
835
- rb_define_method(cBackend, "write", Backend_write_m, -1);
836
1120
  rb_define_method(cBackend, "accept", Backend_accept, 2);
837
1121
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
838
1122
  rb_define_method(cBackend, "connect", Backend_connect, 3);
1123
+ rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
1124
+ rb_define_method(cBackend, "read", Backend_read, 4);
1125
+ rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
839
1126
  rb_define_method(cBackend, "recv", Backend_recv, 3);
840
1127
  rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
841
- rb_define_method(cBackend, "send", Backend_write, 2);
842
- rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1128
+ rb_define_method(cBackend, "recv_feed_loop", Backend_feed_loop, 3);
1129
+ rb_define_method(cBackend, "send", Backend_send, 3);
1130
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
843
1131
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
844
- rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
845
1132
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
846
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1133
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
847
1134
  rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
848
-
849
- rb_define_method(cBackend, "kind", Backend_kind, 0);
1135
+ rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1136
+ rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1137
+ rb_define_method(cBackend, "write", Backend_write_m, -1);
850
1138
 
851
1139
  ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
852
1140
  SYM_libev = ID2SYM(rb_intern("libev"));