polyphony 0.50.1 → 0.53.2

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +23 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +37 -6
  6. data/examples/core/forking.rb +2 -2
  7. data/examples/io/echo_server.rb +1 -0
  8. data/examples/io/tcp_proxy.rb +2 -2
  9. data/ext/polyphony/backend_common.h +36 -6
  10. data/ext/polyphony/backend_io_uring.c +216 -21
  11. data/ext/polyphony/backend_io_uring_context.c +1 -0
  12. data/ext/polyphony/backend_io_uring_context.h +1 -0
  13. data/ext/polyphony/backend_libev.c +362 -20
  14. data/ext/polyphony/event.c +1 -1
  15. data/ext/polyphony/extconf.rb +9 -2
  16. data/ext/polyphony/polyphony.c +102 -0
  17. data/ext/polyphony/polyphony.h +32 -2
  18. data/ext/polyphony/polyphony_ext.c +3 -0
  19. data/ext/polyphony/queue.c +1 -1
  20. data/ext/polyphony/runqueue.c +1 -1
  21. data/ext/polyphony/socket_extensions.c +33 -0
  22. data/ext/polyphony/thread.c +8 -2
  23. data/lib/polyphony/adapters/irb.rb +1 -1
  24. data/lib/polyphony/adapters/mysql2.rb +1 -1
  25. data/lib/polyphony/adapters/postgres.rb +5 -5
  26. data/lib/polyphony/adapters/process.rb +4 -4
  27. data/lib/polyphony/core/global_api.rb +5 -5
  28. data/lib/polyphony/core/sync.rb +1 -1
  29. data/lib/polyphony/core/throttler.rb +1 -1
  30. data/lib/polyphony/core/timer.rb +2 -2
  31. data/lib/polyphony/extensions/core.rb +1 -1
  32. data/lib/polyphony/extensions/io.rb +21 -22
  33. data/lib/polyphony/extensions/openssl.rb +6 -6
  34. data/lib/polyphony/extensions/socket.rb +56 -47
  35. data/lib/polyphony/version.rb +1 -1
  36. data/polyphony.gemspec +6 -5
  37. data/test/helper.rb +1 -1
  38. data/test/stress.rb +2 -0
  39. data/test/test_backend.rb +152 -5
  40. data/test/test_global_api.rb +2 -2
  41. data/test/test_io.rb +84 -1
  42. data/test/test_kernel.rb +1 -1
  43. data/test/test_signal.rb +1 -1
  44. data/test/test_socket.rb +61 -0
  45. data/test/test_thread.rb +4 -0
  46. data/test/test_timer.rb +1 -1
  47. metadata +19 -60
@@ -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,10 +1,52 @@
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>
@@ -16,6 +58,9 @@
16
58
  #include "ruby/io.h"
17
59
 
18
60
  VALUE SYM_libev;
61
+ VALUE SYM_send;
62
+ VALUE SYM_splice;
63
+ VALUE SYM_write;
19
64
 
20
65
  ID ID_ivar_is_nonblocking;
21
66
 
@@ -76,17 +121,27 @@ void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, in
76
121
  // of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
77
122
  }
78
123
 
124
+ inline struct ev_loop *libev_new_loop() {
125
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
126
+ return ev_loop_new(EVFLAG_NOSIGMASK);
127
+ #else
128
+ int is_main_thread = (rb_thread_current() == rb_thread_main());
129
+ return is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
130
+ #endif
131
+ }
132
+
79
133
  static VALUE Backend_initialize(VALUE self) {
80
134
  Backend_t *backend;
81
- VALUE thread = rb_thread_current();
82
- int is_main_thread = (thread == rb_thread_main());
83
-
135
+
84
136
  GetBackend(self, backend);
85
- backend->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
137
+ backend->ev_loop = libev_new_loop();
86
138
 
139
+ // start async watcher used for breaking a poll op (from another thread)
87
140
  ev_async_init(&backend->break_async, break_async_callback);
88
141
  ev_async_start(backend->ev_loop, &backend->break_async);
89
- ev_unref(backend->ev_loop); // don't count the break_async watcher
142
+ // the break_async watcher is unreferenced, in order for Backend_poll to not
143
+ // block when no other watcher is active
144
+ ev_unref(backend->ev_loop);
90
145
 
91
146
  backend->currently_polling = 0;
92
147
  backend->pending_count = 0;
@@ -334,6 +389,58 @@ error:
334
389
  return RAISE_EXCEPTION(switchpoint_result);
335
390
  }
336
391
 
392
+ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
393
+ Backend_t *backend;
394
+ struct libev_io watcher;
395
+ rb_io_t *fptr;
396
+ VALUE str;
397
+ long total;
398
+ long len = 8192;
399
+ int shrinkable;
400
+ char *buf;
401
+ VALUE switchpoint_result = Qnil;
402
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
403
+ ID method_id = SYM2ID(method);
404
+
405
+ READ_LOOP_PREPARE_STR();
406
+
407
+ GetBackend(self, backend);
408
+ if (underlying_io != Qnil) io = underlying_io;
409
+ GetOpenFile(io, fptr);
410
+ rb_io_check_byte_readable(fptr);
411
+ io_set_nonblock(fptr, io);
412
+ rectify_io_file_pos(fptr);
413
+ watcher.fiber = Qnil;
414
+
415
+ while (1) {
416
+ ssize_t n = read(fptr->fd, buf, len);
417
+ if (n < 0) {
418
+ int e = errno;
419
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
420
+
421
+ switchpoint_result = libev_wait_fd_with_watcher(backend, fptr->fd, &watcher, EV_READ);
422
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
423
+ }
424
+ else {
425
+ switchpoint_result = backend_snooze();
426
+
427
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
428
+
429
+ if (n == 0) break; // EOF
430
+ total = n;
431
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
432
+ }
433
+ }
434
+
435
+ RB_GC_GUARD(str);
436
+ RB_GC_GUARD(watcher.fiber);
437
+ RB_GC_GUARD(switchpoint_result);
438
+
439
+ return io;
440
+ error:
441
+ return RAISE_EXCEPTION(switchpoint_result);
442
+ }
443
+
337
444
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
338
445
  Backend_t *backend;
339
446
  struct libev_io watcher;
@@ -620,6 +727,167 @@ error:
620
727
  return RAISE_EXCEPTION(switchpoint_result);
621
728
  }
622
729
 
730
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
731
+ Backend_t *backend;
732
+ struct libev_io watcher;
733
+ rb_io_t *fptr;
734
+ VALUE switchpoint_result = Qnil;
735
+ VALUE underlying_io;
736
+ char *buf = StringValuePtr(str);
737
+ long len = RSTRING_LEN(str);
738
+ long left = len;
739
+ int flags_int = NUM2INT(flags);
740
+
741
+ underlying_io = rb_ivar_get(io, ID_ivar_io);
742
+ if (underlying_io != Qnil) io = underlying_io;
743
+ GetBackend(self, backend);
744
+ io = rb_io_get_write_io(io);
745
+ GetOpenFile(io, fptr);
746
+ io_set_nonblock(fptr, io);
747
+ watcher.fiber = Qnil;
748
+
749
+ while (left > 0) {
750
+ ssize_t n = send(fptr->fd, buf, left, flags_int);
751
+ if (n < 0) {
752
+ int e = errno;
753
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
754
+
755
+ switchpoint_result = libev_wait_fd_with_watcher(backend, fptr->fd, &watcher, EV_WRITE);
756
+
757
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
758
+ }
759
+ else {
760
+ buf += n;
761
+ left -= n;
762
+ }
763
+ }
764
+
765
+ if (watcher.fiber == Qnil) {
766
+ switchpoint_result = backend_snooze();
767
+
768
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
769
+ }
770
+
771
+ RB_GC_GUARD(watcher.fiber);
772
+ RB_GC_GUARD(switchpoint_result);
773
+
774
+ return INT2NUM(len);
775
+ error:
776
+ return RAISE_EXCEPTION(switchpoint_result);
777
+ }
778
+
779
+ #ifdef POLYPHONY_LINUX
780
+ VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
781
+ Backend_t *backend;
782
+ struct libev_io watcher;
783
+ VALUE switchpoint_result = Qnil;
784
+ VALUE underlying_io;
785
+ rb_io_t *src_fptr;
786
+ rb_io_t *dest_fptr;
787
+ int len;
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
+ GetBackend(self, backend);
844
+
845
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
846
+ if (underlying_io != Qnil) src = underlying_io;
847
+ GetOpenFile(src, src_fptr);
848
+ io_set_nonblock(src_fptr, src);
849
+
850
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
851
+ if (underlying_io != Qnil) dest = underlying_io;
852
+ dest = rb_io_get_write_io(dest);
853
+ GetOpenFile(dest, dest_fptr);
854
+ io_set_nonblock(dest_fptr, dest);
855
+
856
+ watcher.fiber = Qnil;
857
+ while (1) {
858
+ len = splice(src_fptr->fd, 0, dest_fptr->fd, 0, NUM2INT(maxlen), 0);
859
+ if (len < 0) {
860
+ int e = errno;
861
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
862
+
863
+ switchpoint_result = libev_wait_fd_with_watcher(backend, src_fptr->fd, &watcher, EV_READ);
864
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
865
+
866
+ switchpoint_result = libev_wait_fd_with_watcher(backend, dest_fptr->fd, &watcher, EV_WRITE);
867
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
868
+ }
869
+ else if (len == 0) {
870
+ break;
871
+ }
872
+ else {
873
+ total += len;
874
+ }
875
+ }
876
+
877
+ if (watcher.fiber == Qnil) {
878
+ switchpoint_result = backend_snooze();
879
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
880
+ }
881
+
882
+ RB_GC_GUARD(watcher.fiber);
883
+ RB_GC_GUARD(switchpoint_result);
884
+
885
+ return INT2NUM(total);
886
+ error:
887
+ return RAISE_EXCEPTION(switchpoint_result);
888
+ }
889
+ #endif
890
+
623
891
  VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
624
892
  Backend_t *backend;
625
893
  rb_io_t *fptr;
@@ -747,6 +1015,7 @@ VALUE Backend_timeout(int argc,VALUE *argv, VALUE self) {
747
1015
  return result;
748
1016
  }
749
1017
 
1018
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
750
1019
  VALUE Backend_waitpid(VALUE self, VALUE pid) {
751
1020
  int pid_int = NUM2INT(pid);
752
1021
  int fd = pidfd_open(pid_int, 0);
@@ -759,18 +1028,53 @@ VALUE Backend_waitpid(VALUE self, VALUE pid) {
759
1028
  RAISE_IF_EXCEPTION(resume_value);
760
1029
  RB_GC_GUARD(resume_value);
761
1030
  }
1031
+ else {
1032
+ int e = errno;
1033
+ rb_syserr_fail(e, strerror(e));
1034
+ }
762
1035
 
763
1036
  int status = 0;
764
1037
  pid_t ret = waitpid(pid_int, &status, WNOHANG);
765
1038
  if (ret < 0) {
766
1039
  int e = errno;
767
- if (e == ECHILD)
768
- ret = pid_int;
769
- else
770
- rb_syserr_fail(e, strerror(e));
1040
+ rb_syserr_fail(e, strerror(e));
771
1041
  }
772
1042
  return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
773
1043
  }
1044
+ #else
1045
+ struct libev_child {
1046
+ struct ev_child child;
1047
+ VALUE fiber;
1048
+ };
1049
+
1050
+ void Backend_child_callback(EV_P_ ev_child *w, int revents) {
1051
+ struct libev_child *watcher = (struct libev_child *)w;
1052
+ int exit_status = WEXITSTATUS(w->rstatus);
1053
+ VALUE status;
1054
+
1055
+ status = rb_ary_new_from_args(2, INT2NUM(w->rpid), INT2NUM(exit_status));
1056
+ Fiber_make_runnable(watcher->fiber, status);
1057
+ }
1058
+
1059
+ VALUE Backend_waitpid(VALUE self, VALUE pid) {
1060
+ Backend_t *backend;
1061
+ struct libev_child watcher;
1062
+ VALUE switchpoint_result = Qnil;
1063
+ GetBackend(self, backend);
1064
+
1065
+ watcher.fiber = rb_fiber_current();
1066
+ ev_child_init(&watcher.child, Backend_child_callback, NUM2INT(pid), 0);
1067
+ ev_child_start(backend->ev_loop, &watcher.child);
1068
+
1069
+ switchpoint_result = backend_await(backend);
1070
+
1071
+ ev_child_stop(backend->ev_loop, &watcher.child);
1072
+ RAISE_IF_EXCEPTION(switchpoint_result);
1073
+ RB_GC_GUARD(watcher.fiber);
1074
+ RB_GC_GUARD(switchpoint_result);
1075
+ return switchpoint_result;
1076
+ }
1077
+ #endif
774
1078
 
775
1079
  void Backend_async_callback(EV_P_ ev_async *w, int revents) { }
776
1080
 
@@ -796,10 +1100,35 @@ VALUE Backend_kind(VALUE self) {
796
1100
  return SYM_libev;
797
1101
  }
798
1102
 
1103
+ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1104
+ VALUE result = Qnil;
1105
+ if (argc == 0) return result;
1106
+
1107
+ for (int i = 0; i < argc; i++) {
1108
+ VALUE op = argv[i];
1109
+ VALUE op_type = RARRAY_AREF(op, 0);
1110
+ VALUE op_len = RARRAY_LEN(op);
1111
+
1112
+ if (op_type == SYM_write && op_len == 3)
1113
+ result = Backend_write(self, RARRAY_AREF(op, 1), RARRAY_AREF(op, 2));
1114
+ else if (op_type == SYM_send && op_len == 4)
1115
+ result = Backend_send(self, RARRAY_AREF(op, 1), RARRAY_AREF(op, 2), RARRAY_AREF(op, 3));
1116
+ #ifdef POLYPHONY_LINUX
1117
+ else if (op_type == SYM_splice && op_len == 4)
1118
+ result = Backend_splice(self, RARRAY_AREF(op, 1), RARRAY_AREF(op, 2), RARRAY_AREF(op, 3));
1119
+ #endif
1120
+ else
1121
+ rb_raise(rb_eRuntimeError, "Invalid op specified or bad op arity");
1122
+ }
1123
+
1124
+ RB_GC_GUARD(result);
1125
+ return result;
1126
+ }
1127
+
799
1128
  void Init_Backend() {
800
1129
  ev_set_allocator(xrealloc);
801
1130
 
802
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1131
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
803
1132
  rb_define_alloc_func(cBackend, Backend_allocate);
804
1133
 
805
1134
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -808,27 +1137,40 @@ void Init_Backend() {
808
1137
 
809
1138
  rb_define_method(cBackend, "poll", Backend_poll, 3);
810
1139
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1140
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
1141
+ rb_define_method(cBackend, "chain", Backend_chain, -1);
811
1142
 
812
- rb_define_method(cBackend, "read", Backend_read, 4);
813
- rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
814
- rb_define_method(cBackend, "write", Backend_write_m, -1);
815
1143
  rb_define_method(cBackend, "accept", Backend_accept, 2);
816
1144
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
817
1145
  rb_define_method(cBackend, "connect", Backend_connect, 3);
1146
+ rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
1147
+ rb_define_method(cBackend, "read", Backend_read, 4);
1148
+ rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
818
1149
  rb_define_method(cBackend, "recv", Backend_recv, 3);
819
1150
  rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
820
- rb_define_method(cBackend, "send", Backend_write, 2);
821
- rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1151
+ rb_define_method(cBackend, "recv_feed_loop", Backend_feed_loop, 3);
1152
+ rb_define_method(cBackend, "send", Backend_send, 3);
1153
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
822
1154
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
823
- rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
1155
+
1156
+ #ifdef POLYPHONY_LINUX
1157
+ rb_define_method(cBackend, "splice", Backend_splice, 3);
1158
+ rb_define_method(cBackend, "splice_to_eof", Backend_splice_to_eof, 3);
1159
+ #endif
1160
+
824
1161
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
825
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1162
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
826
1163
  rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
827
-
828
- rb_define_method(cBackend, "kind", Backend_kind, 0);
1164
+ rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1165
+ rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1166
+ rb_define_method(cBackend, "write", Backend_write_m, -1);
829
1167
 
830
1168
  ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
831
1169
  SYM_libev = ID2SYM(rb_intern("libev"));
1170
+
1171
+ SYM_send = ID2SYM(rb_intern("send"));
1172
+ SYM_splice = ID2SYM(rb_intern("splice"));
1173
+ SYM_write = ID2SYM(rb_intern("write"));
832
1174
  }
833
1175
 
834
1176
  #endif // POLYPHONY_BACKEND_LIBEV