polyphony 0.50.0 → 0.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +30 -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 +57 -7
  10. data/ext/polyphony/backend_io_uring.c +232 -49
  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 +355 -34
  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,19 +1,66 @@
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"
14
58
  #include "ruby/io.h"
15
59
 
16
60
  VALUE SYM_libev;
61
+ VALUE SYM_send;
62
+ VALUE SYM_splice;
63
+ VALUE SYM_write;
17
64
 
18
65
  ID ID_ivar_is_nonblocking;
19
66
 
@@ -74,17 +121,27 @@ void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, in
74
121
  // of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
75
122
  }
76
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
+
77
133
  static VALUE Backend_initialize(VALUE self) {
78
134
  Backend_t *backend;
79
- VALUE thread = rb_thread_current();
80
- int is_main_thread = (thread == rb_thread_main());
81
-
135
+
82
136
  GetBackend(self, backend);
83
- backend->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
137
+ backend->ev_loop = libev_new_loop();
84
138
 
139
+ // start async watcher used for breaking a poll op (from another thread)
85
140
  ev_async_init(&backend->break_async, break_async_callback);
86
141
  ev_async_start(backend->ev_loop, &backend->break_async);
87
- 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);
88
145
 
89
146
  backend->currently_polling = 0;
90
147
  backend->pending_count = 0;
@@ -332,6 +389,58 @@ error:
332
389
  return RAISE_EXCEPTION(switchpoint_result);
333
390
  }
334
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
+
335
444
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
336
445
  Backend_t *backend;
337
446
  struct libev_io watcher;
@@ -618,6 +727,167 @@ error:
618
727
  return RAISE_EXCEPTION(switchpoint_result);
619
728
  }
620
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
+ #ifndef 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
+
621
891
  VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
622
892
  Backend_t *backend;
623
893
  rb_io_t *fptr;
@@ -685,26 +955,12 @@ noreturn VALUE Backend_timer_loop(VALUE self, VALUE interval) {
685
955
  RB_GC_GUARD(switchpoint_result);
686
956
 
687
957
  rb_yield(Qnil);
688
-
689
- while (1) {
958
+ do {
690
959
  next_time += interval_d;
691
- if (next_time > now) break;
692
- }
960
+ } while (next_time <= now);
693
961
  }
694
962
  }
695
963
 
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
964
  struct libev_timeout {
709
965
  struct ev_timer timer;
710
966
  VALUE fiber;
@@ -759,13 +1015,39 @@ VALUE Backend_timeout(int argc,VALUE *argv, VALUE self) {
759
1015
  return result;
760
1016
  }
761
1017
 
1018
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
1019
+ VALUE Backend_waitpid(VALUE self, VALUE pid) {
1020
+ int pid_int = NUM2INT(pid);
1021
+ int fd = pidfd_open(pid_int, 0);
1022
+ if (fd >= 0) {
1023
+ Backend_t *backend;
1024
+ GetBackend(self, backend);
1025
+
1026
+ VALUE resume_value = libev_wait_fd(backend, fd, EV_READ, 0);
1027
+ close(fd);
1028
+ RAISE_IF_EXCEPTION(resume_value);
1029
+ RB_GC_GUARD(resume_value);
1030
+ }
1031
+ else {
1032
+ int e = errno;
1033
+ rb_syserr_fail(e, strerror(e));
1034
+ }
1035
+
1036
+ int status = 0;
1037
+ pid_t ret = waitpid(pid_int, &status, WNOHANG);
1038
+ if (ret < 0) {
1039
+ int e = errno;
1040
+ rb_syserr_fail(e, strerror(e));
1041
+ }
1042
+ return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
1043
+ }
1044
+ #else
762
1045
  struct libev_child {
763
1046
  struct ev_child child;
764
1047
  VALUE fiber;
765
1048
  };
766
1049
 
767
- void Backend_child_callback(EV_P_ ev_child *w, int revents)
768
- {
1050
+ void Backend_child_callback(EV_P_ ev_child *w, int revents) {
769
1051
  struct libev_child *watcher = (struct libev_child *)w;
770
1052
  int exit_status = WEXITSTATUS(w->rstatus);
771
1053
  VALUE status;
@@ -792,6 +1074,7 @@ VALUE Backend_waitpid(VALUE self, VALUE pid) {
792
1074
  RB_GC_GUARD(switchpoint_result);
793
1075
  return switchpoint_result;
794
1076
  }
1077
+ #endif
795
1078
 
796
1079
  void Backend_async_callback(EV_P_ ev_async *w, int revents) { }
797
1080
 
@@ -817,10 +1100,35 @@ VALUE Backend_kind(VALUE self) {
817
1100
  return SYM_libev;
818
1101
  }
819
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
+ #ifndef 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
+
820
1128
  void Init_Backend() {
821
1129
  ev_set_allocator(xrealloc);
822
1130
 
823
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1131
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
824
1132
  rb_define_alloc_func(cBackend, Backend_allocate);
825
1133
 
826
1134
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -829,27 +1137,40 @@ void Init_Backend() {
829
1137
 
830
1138
  rb_define_method(cBackend, "poll", Backend_poll, 3);
831
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);
832
1142
 
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
1143
  rb_define_method(cBackend, "accept", Backend_accept, 2);
837
1144
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
838
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);
839
1149
  rb_define_method(cBackend, "recv", Backend_recv, 3);
840
1150
  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);
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);
843
1154
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
844
- rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
1155
+
1156
+ #ifndef 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
+
845
1161
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
846
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1162
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
847
1163
  rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
848
-
849
- 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);
850
1167
 
851
1168
  ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
852
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"));
853
1174
  }
854
1175
 
855
1176
  #endif // POLYPHONY_BACKEND_LIBEV