polyphony 0.49.1 → 0.52.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +35 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +6 -0
  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/performance/thread-vs-fiber/polyphony_server.rb +2 -4
  11. data/ext/polyphony/backend_common.h +58 -8
  12. data/ext/polyphony/backend_io_uring.c +158 -35
  13. data/ext/polyphony/backend_libev.c +192 -25
  14. data/ext/polyphony/event.c +1 -1
  15. data/ext/polyphony/extconf.rb +7 -2
  16. data/ext/polyphony/fiber.c +2 -1
  17. data/ext/polyphony/polyphony.c +94 -0
  18. data/ext/polyphony/polyphony.h +29 -2
  19. data/ext/polyphony/queue.c +1 -1
  20. data/ext/polyphony/runqueue.c +7 -1
  21. data/ext/polyphony/runqueue_ring_buffer.c +9 -0
  22. data/ext/polyphony/runqueue_ring_buffer.h +1 -0
  23. data/ext/polyphony/thread.c +14 -0
  24. data/lib/polyphony/adapters/irb.rb +1 -1
  25. data/lib/polyphony/adapters/mysql2.rb +1 -1
  26. data/lib/polyphony/adapters/postgres.rb +5 -5
  27. data/lib/polyphony/adapters/process.rb +4 -4
  28. data/lib/polyphony/core/exceptions.rb +1 -0
  29. data/lib/polyphony/core/global_api.rb +6 -6
  30. data/lib/polyphony/core/sync.rb +1 -1
  31. data/lib/polyphony/core/throttler.rb +1 -1
  32. data/lib/polyphony/core/timer.rb +63 -20
  33. data/lib/polyphony/extensions/core.rb +5 -5
  34. data/lib/polyphony/extensions/fiber.rb +11 -8
  35. data/lib/polyphony/extensions/io.rb +13 -22
  36. data/lib/polyphony/extensions/openssl.rb +6 -6
  37. data/lib/polyphony/extensions/socket.rb +41 -41
  38. data/lib/polyphony/extensions/thread.rb +1 -2
  39. data/lib/polyphony/version.rb +1 -1
  40. data/polyphony.gemspec +6 -5
  41. data/test/helper.rb +2 -3
  42. data/test/stress.rb +2 -0
  43. data/test/test_backend.rb +58 -5
  44. data/test/test_fiber.rb +31 -0
  45. data/test/test_global_api.rb +2 -2
  46. data/test/test_io.rb +84 -1
  47. data/test/test_kernel.rb +1 -1
  48. data/test/test_signal.rb +2 -3
  49. data/test/test_socket.rb +61 -0
  50. data/test/test_timer.rb +41 -8
  51. metadata +21 -60
@@ -1,3 +1,41 @@
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
 
3
41
  #include <netdb.h>
@@ -8,6 +46,8 @@
8
46
  #include <netinet/in.h>
9
47
  #include <arpa/inet.h>
10
48
  #include <stdnoreturn.h>
49
+ #include <sys/types.h>
50
+ #include <sys/wait.h>
11
51
 
12
52
  #include "polyphony.h"
13
53
  #include "../libev/ev.h"
@@ -74,17 +114,27 @@ void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, in
74
114
  // of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
75
115
  }
76
116
 
117
+ inline struct ev_loop *libev_new_loop() {
118
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
119
+ return ev_loop_new(EVFLAG_NOSIGMASK);
120
+ #else
121
+ int is_main_thread = (rb_thread_current() == rb_thread_main());
122
+ return is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
123
+ #endif
124
+ }
125
+
77
126
  static VALUE Backend_initialize(VALUE self) {
78
127
  Backend_t *backend;
79
- VALUE thread = rb_thread_current();
80
- int is_main_thread = (thread == rb_thread_main());
81
-
128
+
82
129
  GetBackend(self, backend);
83
- backend->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
130
+ backend->ev_loop = libev_new_loop();
84
131
 
132
+ // start async watcher used for breaking a poll op (from another thread)
85
133
  ev_async_init(&backend->break_async, break_async_callback);
86
134
  ev_async_start(backend->ev_loop, &backend->break_async);
87
- ev_unref(backend->ev_loop); // don't count the break_async watcher
135
+ // the break_async watcher is unreferenced, in order for Backend_poll to not
136
+ // block when no other watcher is active
137
+ ev_unref(backend->ev_loop);
88
138
 
89
139
  backend->currently_polling = 0;
90
140
  backend->pending_count = 0;
@@ -332,6 +382,58 @@ error:
332
382
  return RAISE_EXCEPTION(switchpoint_result);
333
383
  }
334
384
 
385
+ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
386
+ Backend_t *backend;
387
+ struct libev_io watcher;
388
+ rb_io_t *fptr;
389
+ VALUE str;
390
+ long total;
391
+ long len = 8192;
392
+ int shrinkable;
393
+ char *buf;
394
+ VALUE switchpoint_result = Qnil;
395
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
396
+ ID method_id = SYM2ID(method);
397
+
398
+ READ_LOOP_PREPARE_STR();
399
+
400
+ GetBackend(self, backend);
401
+ if (underlying_io != Qnil) io = underlying_io;
402
+ GetOpenFile(io, fptr);
403
+ rb_io_check_byte_readable(fptr);
404
+ io_set_nonblock(fptr, io);
405
+ rectify_io_file_pos(fptr);
406
+ watcher.fiber = Qnil;
407
+
408
+ while (1) {
409
+ ssize_t n = read(fptr->fd, buf, len);
410
+ if (n < 0) {
411
+ int e = errno;
412
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
413
+
414
+ switchpoint_result = libev_wait_fd_with_watcher(backend, fptr->fd, &watcher, EV_READ);
415
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
416
+ }
417
+ else {
418
+ switchpoint_result = backend_snooze();
419
+
420
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
421
+
422
+ if (n == 0) break; // EOF
423
+ total = n;
424
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
425
+ }
426
+ }
427
+
428
+ RB_GC_GUARD(str);
429
+ RB_GC_GUARD(watcher.fiber);
430
+ RB_GC_GUARD(switchpoint_result);
431
+
432
+ return io;
433
+ error:
434
+ return RAISE_EXCEPTION(switchpoint_result);
435
+ }
436
+
335
437
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
336
438
  Backend_t *backend;
337
439
  struct libev_io watcher;
@@ -618,6 +720,55 @@ error:
618
720
  return RAISE_EXCEPTION(switchpoint_result);
619
721
  }
620
722
 
723
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
724
+ Backend_t *backend;
725
+ struct libev_io watcher;
726
+ rb_io_t *fptr;
727
+ VALUE switchpoint_result = Qnil;
728
+ VALUE underlying_io;
729
+ char *buf = StringValuePtr(str);
730
+ long len = RSTRING_LEN(str);
731
+ long left = len;
732
+ int flags_int = NUM2INT(flags);
733
+
734
+ underlying_io = rb_ivar_get(io, ID_ivar_io);
735
+ if (underlying_io != Qnil) io = underlying_io;
736
+ GetBackend(self, backend);
737
+ io = rb_io_get_write_io(io);
738
+ GetOpenFile(io, fptr);
739
+ io_set_nonblock(fptr, io);
740
+ watcher.fiber = Qnil;
741
+
742
+ while (left > 0) {
743
+ ssize_t n = send(fptr->fd, buf, left, flags_int);
744
+ if (n < 0) {
745
+ int e = errno;
746
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
747
+
748
+ switchpoint_result = libev_wait_fd_with_watcher(backend, fptr->fd, &watcher, EV_WRITE);
749
+
750
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
751
+ }
752
+ else {
753
+ buf += n;
754
+ left -= n;
755
+ }
756
+ }
757
+
758
+ if (watcher.fiber == Qnil) {
759
+ switchpoint_result = backend_snooze();
760
+
761
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
762
+ }
763
+
764
+ RB_GC_GUARD(watcher.fiber);
765
+ RB_GC_GUARD(switchpoint_result);
766
+
767
+ return INT2NUM(len);
768
+ error:
769
+ return RAISE_EXCEPTION(switchpoint_result);
770
+ }
771
+
621
772
  VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
622
773
  Backend_t *backend;
623
774
  rb_io_t *fptr;
@@ -685,26 +836,12 @@ noreturn VALUE Backend_timer_loop(VALUE self, VALUE interval) {
685
836
  RB_GC_GUARD(switchpoint_result);
686
837
 
687
838
  rb_yield(Qnil);
688
-
689
- while (1) {
839
+ do {
690
840
  next_time += interval_d;
691
- if (next_time > now) break;
692
- }
841
+ } while (next_time <= now);
693
842
  }
694
843
  }
695
844
 
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
845
  struct libev_timeout {
709
846
  struct ev_timer timer;
710
847
  VALUE fiber;
@@ -759,13 +896,39 @@ VALUE Backend_timeout(int argc,VALUE *argv, VALUE self) {
759
896
  return result;
760
897
  }
761
898
 
899
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
900
+ VALUE Backend_waitpid(VALUE self, VALUE pid) {
901
+ int pid_int = NUM2INT(pid);
902
+ int fd = pidfd_open(pid_int, 0);
903
+ if (fd >= 0) {
904
+ Backend_t *backend;
905
+ GetBackend(self, backend);
906
+
907
+ VALUE resume_value = libev_wait_fd(backend, fd, EV_READ, 0);
908
+ close(fd);
909
+ RAISE_IF_EXCEPTION(resume_value);
910
+ RB_GC_GUARD(resume_value);
911
+ }
912
+ else {
913
+ int e = errno;
914
+ rb_syserr_fail(e, strerror(e));
915
+ }
916
+
917
+ int status = 0;
918
+ pid_t ret = waitpid(pid_int, &status, WNOHANG);
919
+ if (ret < 0) {
920
+ int e = errno;
921
+ rb_syserr_fail(e, strerror(e));
922
+ }
923
+ return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
924
+ }
925
+ #else
762
926
  struct libev_child {
763
927
  struct ev_child child;
764
928
  VALUE fiber;
765
929
  };
766
930
 
767
- void Backend_child_callback(EV_P_ ev_child *w, int revents)
768
- {
931
+ void Backend_child_callback(EV_P_ ev_child *w, int revents) {
769
932
  struct libev_child *watcher = (struct libev_child *)w;
770
933
  int exit_status = WEXITSTATUS(w->rstatus);
771
934
  VALUE status;
@@ -792,6 +955,7 @@ VALUE Backend_waitpid(VALUE self, VALUE pid) {
792
955
  RB_GC_GUARD(switchpoint_result);
793
956
  return switchpoint_result;
794
957
  }
958
+ #endif
795
959
 
796
960
  void Backend_async_callback(EV_P_ ev_async *w, int revents) { }
797
961
 
@@ -820,7 +984,7 @@ VALUE Backend_kind(VALUE self) {
820
984
  void Init_Backend() {
821
985
  ev_set_allocator(xrealloc);
822
986
 
823
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
987
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
824
988
  rb_define_alloc_func(cBackend, Backend_allocate);
825
989
 
826
990
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -832,13 +996,16 @@ void Init_Backend() {
832
996
 
833
997
  rb_define_method(cBackend, "read", Backend_read, 4);
834
998
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
999
+ rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
835
1000
  rb_define_method(cBackend, "write", Backend_write_m, -1);
836
1001
  rb_define_method(cBackend, "accept", Backend_accept, 2);
837
1002
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
838
1003
  rb_define_method(cBackend, "connect", Backend_connect, 3);
839
1004
  rb_define_method(cBackend, "recv", Backend_recv, 3);
840
1005
  rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
841
- rb_define_method(cBackend, "send", Backend_write, 2);
1006
+ rb_define_method(cBackend, "recv_feed_loop", Backend_feed_loop, 3);
1007
+ rb_define_method(cBackend, "send", Backend_send, 3);
1008
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
842
1009
  rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
843
1010
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
844
1011
  rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
@@ -77,7 +77,7 @@ VALUE Event_await(VALUE self) {
77
77
  }
78
78
 
79
79
  void Init_Event() {
80
- cEvent = rb_define_class_under(mPolyphony, "Event", rb_cData);
80
+ cEvent = rb_define_class_under(mPolyphony, "Event", rb_cObject);
81
81
  rb_define_alloc_func(cEvent, Event_allocate);
82
82
 
83
83
  rb_define_method(cEvent, "initialize", Event_initialize, 0);
@@ -4,15 +4,19 @@ require 'rubygems'
4
4
  require 'mkmf'
5
5
 
6
6
  use_liburing = false
7
+ use_pidfd_open = false
7
8
  force_use_libev = ENV['POLYPHONY_USE_LIBEV'] != nil
8
9
 
9
- if !force_use_libev && RUBY_PLATFORM =~ /linux/ && `uname -sr` =~ /Linux 5\.([\d+])/
10
+ if RUBY_PLATFORM =~ /linux/ && `uname -sr` =~ /Linux 5\.([\d+])/
10
11
  kernel_minor_version = $1.gsub('.', '').to_i
11
- use_liburing = kernel_minor_version >= 6
12
+ use_liburing = !force_use_libev && kernel_minor_version >= 6
13
+ use_pidfd_open = kernel_minor_version >= 3
12
14
  end
13
15
 
16
+ $defs << '-DPOLYPHONY_USE_PIDFD_OPEN' if use_pidfd_open
14
17
  if use_liburing
15
18
  $defs << "-DPOLYPHONY_BACKEND_LIBURING"
19
+ $defs << "-DPOLYPHONY_UNSET_NONBLOCK" if RUBY_VERSION =~ /^3/
16
20
  $CFLAGS << " -Wno-pointer-arith"
17
21
  else
18
22
  $defs << "-DPOLYPHONY_BACKEND_LIBEV"
@@ -23,6 +27,7 @@ else
23
27
  $defs << '-DEV_USE_KQUEUE' if have_header('sys/event.h') && have_header('sys/queue.h')
24
28
  $defs << '-DEV_USE_PORT' if have_type('port_event_t', 'port.h')
25
29
  $defs << '-DHAVE_SYS_RESOURCE_H' if have_header('sys/resource.h')
30
+
26
31
  $CFLAGS << " -Wno-comment"
27
32
  $CFLAGS << " -Wno-unused-result"
28
33
  $CFLAGS << " -Wno-dangling-else"
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
101
101
  }
102
102
  rb_hash_aset(waiting_fibers, fiber, Qtrue);
103
103
 
104
- result = Thread_switch_fiber(rb_thread_current());
104
+ VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
105
+ result = Backend_wait_event(backend, Qnil);
105
106
 
106
107
  rb_hash_delete(waiting_fibers, fiber);
107
108
  RAISE_IF_EXCEPTION(result);
@@ -46,11 +46,105 @@ VALUE Polyphony_trace(VALUE self, VALUE enabled) {
46
46
  return Qnil;
47
47
  }
48
48
 
49
+ #define BACKEND() (rb_ivar_get(rb_thread_current(), ID_ivar_backend))
50
+
51
+ VALUE Polyphony_backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
52
+ return Backend_accept(BACKEND(), server_socket, socket_class);
53
+ }
54
+
55
+ VALUE Polyphony_backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
56
+ return Backend_accept_loop(BACKEND(), server_socket, socket_class);
57
+ }
58
+
59
+ VALUE Polyphony_backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port) {
60
+ return Backend_connect(BACKEND(), io, addr, port);
61
+ }
62
+
63
+ VALUE Polyphony_backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
64
+ return Backend_feed_loop(BACKEND(), io, receiver, method);
65
+ }
66
+
67
+ VALUE Polyphony_backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
68
+ return Backend_read(BACKEND(), io, str, length, to_eof);
69
+ }
70
+
71
+ VALUE Polyphony_backend_read_loop(VALUE self, VALUE io) {
72
+ return Backend_read_loop(BACKEND(), io);
73
+ }
74
+
75
+ VALUE Polyphony_backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
76
+ return Backend_recv(BACKEND(), io, str, length);
77
+ }
78
+
79
+ VALUE Polyphony_backend_recv_loop(VALUE self, VALUE io) {
80
+ return Backend_recv_loop(BACKEND(), io);
81
+ }
82
+
83
+ VALUE Polyphony_backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
84
+ return Backend_recv_feed_loop(BACKEND(), io, receiver, method);
85
+ }
86
+
87
+ VALUE Polyphony_backend_send(VALUE self, VALUE io, VALUE msg, VALUE flags) {
88
+ return Backend_send(BACKEND(), io, msg, flags);
89
+ }
90
+
91
+ VALUE Polyphony_backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags) {
92
+ return Backend_sendv(BACKEND(), io, ary, flags);
93
+ }
94
+
95
+ VALUE Polyphony_backend_sleep(VALUE self, VALUE duration) {
96
+ return Backend_sleep(BACKEND(), duration);
97
+ }
98
+
99
+ VALUE Polyphony_backend_timeout(int argc,VALUE *argv, VALUE self) {
100
+ return Backend_timeout(argc, argv, BACKEND());
101
+ }
102
+
103
+ VALUE Polyphony_backend_timer_loop(VALUE self, VALUE interval) {
104
+ return Backend_timer_loop(BACKEND(), interval);
105
+ }
106
+
107
+ VALUE Polyphony_backend_wait_event(VALUE self, VALUE raise) {
108
+ return Backend_wait_event(BACKEND(), raise);
109
+ }
110
+
111
+ VALUE Polyphony_backend_wait_io(VALUE self, VALUE io, VALUE write) {
112
+ return Backend_wait_io(BACKEND(), io, write);
113
+ }
114
+
115
+ VALUE Polyphony_backend_waitpid(VALUE self, VALUE pid) {
116
+ return Backend_waitpid(BACKEND(), pid);
117
+ }
118
+
119
+ VALUE Polyphony_backend_write(int argc, VALUE *argv, VALUE self) {
120
+ return Backend_write_m(argc, argv, BACKEND());
121
+ }
122
+
49
123
  void Init_Polyphony() {
50
124
  mPolyphony = rb_define_module("Polyphony");
51
125
 
52
126
  rb_define_singleton_method(mPolyphony, "trace", Polyphony_trace, 1);
53
127
 
128
+ // backend methods
129
+ rb_define_singleton_method(mPolyphony, "backend_accept", Polyphony_backend_accept, 2);
130
+ rb_define_singleton_method(mPolyphony, "backend_accept_loop", Polyphony_backend_accept_loop, 2);
131
+ rb_define_singleton_method(mPolyphony, "backend_connect", Polyphony_backend_connect, 3);
132
+ rb_define_singleton_method(mPolyphony, "backend_feed_loop", Polyphony_backend_feed_loop, 3);
133
+ rb_define_singleton_method(mPolyphony, "backend_read", Polyphony_backend_read, 4);
134
+ rb_define_singleton_method(mPolyphony, "backend_read_loop", Polyphony_backend_read_loop, 1);
135
+ rb_define_singleton_method(mPolyphony, "backend_recv", Polyphony_backend_recv, 3);
136
+ rb_define_singleton_method(mPolyphony, "backend_recv_loop", Polyphony_backend_recv_loop, 1);
137
+ rb_define_singleton_method(mPolyphony, "backend_recv_feed_loop", Polyphony_backend_recv_feed_loop, 3);
138
+ rb_define_singleton_method(mPolyphony, "backend_send", Polyphony_backend_send, 3);
139
+ rb_define_singleton_method(mPolyphony, "backend_sendv", Polyphony_backend_sendv, 3);
140
+ rb_define_singleton_method(mPolyphony, "backend_sleep", Polyphony_backend_sleep, 1);
141
+ rb_define_singleton_method(mPolyphony, "backend_timeout", Polyphony_backend_timeout, -1);
142
+ rb_define_singleton_method(mPolyphony, "backend_timer_loop", Polyphony_backend_timer_loop, 1);
143
+ rb_define_singleton_method(mPolyphony, "backend_wait_event", Polyphony_backend_wait_event, 1);
144
+ rb_define_singleton_method(mPolyphony, "backend_wait_io", Polyphony_backend_wait_io, 2);
145
+ rb_define_singleton_method(mPolyphony, "backend_waitpid", Polyphony_backend_waitpid, 1);
146
+ rb_define_singleton_method(mPolyphony, "backend_write", Polyphony_backend_write, -1);
147
+
54
148
  rb_define_global_function("snooze", Polyphony_snooze, 0);
55
149
  rb_define_global_function("suspend", Polyphony_suspend, 0);
56
150