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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2aac80a08ca16498c3ee0044b0ba6de039b179425051cbcee67af4c9b948c349
4
- data.tar.gz: bbe016d6b2ad3c5c89129089f9ebb8457a37159e03be2ff84f2572454880b0c2
3
+ metadata.gz: b769b5e71cbfa8679a639d369817abd8e5870efec8ea21feb60bb1e46ee7ca29
4
+ data.tar.gz: 0b2ded84eb0e120d20dd17c9973e2cab784cdf61c20bd20fcb6a13c2b6e9f58a
5
5
  SHA512:
6
- metadata.gz: 2f91d0b40bceb4600e4bbd22f39cdbd6bfd4ac5b5fc038f77deb7d26a464ce3cad177884f9aadde60c78bc48df170ab6909c44d133283af3abf77a090ac58332
7
- data.tar.gz: 37dfa412a25f41dd704a203a865817bf273a23db0ccbe1bbec1a3f6017b9cd953301d67077e3b58601485f4dd96db94bef77da3d46cabd57fb563d3652173175
6
+ metadata.gz: e6d3a3d2c130c31483ead4d8f13cc90a87149ac6c461871a43144111cdc37e6e2544c130dc447af26e39e9519b48e3abe1a10197dd4512a7292968e4939e676b
7
+ data.tar.gz: dc1b53918032fc74578ca528abb5a96e423ea8e7a070047f7936720cb6553d9f247cbb625e9d82c7b9b58795a4a3a74aae566231a7a4bb8443f5352c00d10b2a
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest]
11
- ruby: [2.6, 2.7]
11
+ ruby: [2.6, 2.7, 3.0]
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## 0.53.1
2
+
3
+ - Remove `splice` methods on libev backend on non-Linux OS (#43)
4
+
5
+ ## 0.53.0
6
+
7
+ - Implement `Backend#splice`, `Backend#splice_to_eof`, along with `IO#splice`, `IO#splice_to_eof`
8
+
9
+ ## 0.52.0
10
+
11
+ - Polyphony is now compatible with Ruby 3.0
12
+ - Add `Backend#sendv` method for sending multiple strings
13
+ - Accept flags argument in `Backend#send` (#48)
14
+ - Fix io_uring backend on Ruby 3.0 (#47)
15
+ - Implement C-based public backend API: `Polyphony.backend_XXXX` methods
16
+ - libev backend: Use` pidfd_open` for Linux 5.3+, otherwise use a libev child watcher
17
+ - Use `:call` as default method in `#feed_loop`
18
+
19
+ ## 0.51.0
20
+
21
+ - Implement `IO#feed_loop`, `Socket#feed_loop`
22
+ - Fix error handling in `Process.kill_and_await`
23
+
24
+ ## 0.50.1
25
+
26
+ - Set `IOSQE_ASYNC` flag in io_uring backend
27
+ - Fix error handling in `Backend#waitpid`
28
+ - Reimplement libev backend's `#waitpid` by using pidfd_open (in similar manner
29
+ to the io_uring backend)
30
+
1
31
  ## 0.50.0
2
32
 
3
33
  - Use `Process::CLOCK_MONOTONIC` in Timer
data/Gemfile.lock CHANGED
@@ -1,101 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.50.0)
4
+ polyphony (0.53.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- addressable (2.7.0)
10
- public_suffix (>= 2.0.2, < 5.0)
11
9
  ansi (1.5.0)
12
10
  ast (2.4.0)
13
11
  builder (3.2.4)
14
12
  coderay (1.1.3)
15
- colorator (1.1.0)
16
- concurrent-ruby (1.1.6)
17
13
  docile (1.3.2)
18
- em-websocket (0.5.1)
19
- eventmachine (>= 0.12.9)
20
- http_parser.rb (~> 0.6.0)
21
- eventmachine (1.2.7)
22
- ffi (1.12.1)
23
- forwardable-extended (2.6.0)
24
14
  hiredis (0.6.3)
25
15
  http_parser.rb (0.6.0)
26
16
  httparty (0.17.1)
27
17
  mime-types (~> 3.0)
28
18
  multi_xml (>= 0.5.2)
29
- i18n (0.9.5)
30
- concurrent-ruby (~> 1.0)
31
- jekyll (3.8.6)
32
- addressable (~> 2.4)
33
- colorator (~> 1.0)
34
- em-websocket (~> 0.5)
35
- i18n (~> 0.7)
36
- jekyll-sass-converter (~> 1.0)
37
- jekyll-watch (~> 2.0)
38
- kramdown (~> 1.14)
39
- liquid (~> 4.0)
40
- mercenary (~> 0.3.3)
41
- pathutil (~> 0.9)
42
- rouge (>= 1.7, < 4)
43
- safe_yaml (~> 1.0)
44
- jekyll-remote-theme (0.4.1)
45
- addressable (~> 2.0)
46
- jekyll (>= 3.5, < 5.0)
47
- rubyzip (>= 1.3.0)
48
- jekyll-sass-converter (1.5.2)
49
- sass (~> 3.4)
50
- jekyll-seo-tag (2.6.1)
51
- jekyll (>= 3.3, < 5.0)
52
- jekyll-watch (2.2.1)
53
- listen (~> 3.0)
54
19
  json (2.3.0)
55
- just-the-docs (0.3.0)
56
- jekyll (>= 3.8.5)
57
- jekyll-seo-tag (~> 2.0)
58
- rake (>= 12.3.1, < 13.1.0)
59
- kramdown (1.17.0)
60
- liquid (4.0.3)
61
- listen (3.2.1)
62
- rb-fsevent (~> 0.10, >= 0.10.3)
63
- rb-inotify (~> 0.9, >= 0.9.10)
64
- mercenary (0.3.6)
65
20
  method_source (1.0.0)
66
21
  mime-types (3.3.1)
67
22
  mime-types-data (~> 3.2015)
68
23
  mime-types-data (3.2020.0512)
69
- minitest (5.13.0)
24
+ minitest (5.14.4)
70
25
  minitest-reporters (1.4.2)
71
26
  ansi
72
27
  builder
73
28
  minitest (>= 5.0)
74
29
  ruby-progressbar
30
+ msgpack (1.4.2)
75
31
  multi_xml (0.6.0)
76
32
  mysql2 (0.5.3)
77
33
  parallel (1.19.1)
78
34
  parser (2.7.0.2)
79
35
  ast (~> 2.4.0)
80
- pathutil (0.16.2)
81
- forwardable-extended (~> 2.6)
82
36
  pg (1.1.4)
83
37
  pry (0.13.1)
84
38
  coderay (~> 1.1)
85
39
  method_source (~> 1.0)
86
- public_suffix (4.0.3)
87
40
  rack (2.2.3)
88
41
  rainbow (3.0.0)
89
- rake (12.3.3)
42
+ rake (13.0.3)
90
43
  rake-compiler (1.1.1)
91
44
  rake
92
- rb-fsevent (0.10.3)
93
- rb-inotify (0.10.1)
94
- ffi (~> 1.0)
95
45
  redis (4.1.0)
96
46
  regexp_parser (1.7.1)
97
47
  rexml (3.2.4)
98
- rouge (3.15.0)
99
48
  rubocop (0.85.1)
100
49
  parallel (~> 1.10)
101
50
  parser (>= 2.7.0.1)
@@ -108,13 +57,6 @@ GEM
108
57
  rubocop-ast (0.0.3)
109
58
  parser (>= 2.7.0.1)
110
59
  ruby-progressbar (1.10.1)
111
- rubyzip (2.0.0)
112
- safe_yaml (1.0.5)
113
- sass (3.7.4)
114
- sass-listen (~> 4.0.0)
115
- sass-listen (4.0.0)
116
- rb-fsevent (~> 0.9, >= 0.9.4)
117
- rb-inotify (~> 0.9, >= 0.9.7)
118
60
  sequel (5.34.0)
119
61
  simplecov (0.17.1)
120
62
  docile (~> 1.1)
@@ -130,12 +72,9 @@ DEPENDENCIES
130
72
  hiredis (= 0.6.3)
131
73
  http_parser.rb (~> 0.6.0)
132
74
  httparty (= 0.17.1)
133
- jekyll (~> 3.8.6)
134
- jekyll-remote-theme (~> 0.4.1)
135
- jekyll-seo-tag (~> 2.6.1)
136
- just-the-docs (~> 0.3.0)
137
- minitest (= 5.13.0)
75
+ minitest (= 5.14.4)
138
76
  minitest-reporters (= 1.4.2)
77
+ msgpack (= 1.4.2)
139
78
  mysql2 (= 0.5.3)
140
79
  pg (= 1.1.4)
141
80
  polyphony!
@@ -148,4 +87,4 @@ DEPENDENCIES
148
87
  simplecov (= 0.17.1)
149
88
 
150
89
  BUNDLED WITH
151
- 2.1.4
90
+ 2.2.3
data/TODO.md CHANGED
@@ -1,9 +1,11 @@
1
+ - Add support for IPv6:
2
+ https://www.reddit.com/r/ruby/comments/lyen23/understanding_ipv6_and_why_its_important_to_you/
3
+
4
+ - Add support for UDP sockets
5
+
1
6
  - Check segfault when resetting a `cancel_after` timeout lots of times at very high rate
2
7
  - Check why `throttled_loop` inside of `move_on_after` fails to stop
3
8
 
4
- - Commented out `io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);` in `io_uring_backend_defer_submit_and_await`:
5
- - This flag should be set for I/O ops, not for other stuff
6
-
7
9
  - Override stock `::SizedQueue` impl with Queue with capacity
8
10
 
9
11
  - Add support for `break` and `StopIteration` in all loops (with tests)
@@ -16,17 +18,46 @@
16
18
  - `Fiber#receive_loop` (very little effort, should be implemented in C)
17
19
 
18
20
 
19
- - Add `Backend#splice`, `Backend#splice_loop` for implementing stuff like proxying:
21
+ - Add `Backend#splice`, `Backend#splice_to_eof` for implementing stuff like proxying:
20
22
 
21
23
  ```ruby
22
24
  def two_way_proxy(socket1, socket2)
23
25
  backend = Thread.current.backend
24
- f1 = spin { backend.splice_loop(socket1, socket2) }
25
- f2 = spin { backend.splice_loop(socket2, socket1) }
26
+ f1 = spin { backend.splice_to_eof(socket1, socket2) }
27
+ f2 = spin { backend.splice_to_eof(socket2, socket1) }
26
28
  Fiber.await(f1, f2)
27
29
  end
28
30
  ```
29
31
 
32
+ - Add support for `close` to io_uring backend
33
+
34
+ - Add support for submission of multiple requests to io_uring backend:
35
+
36
+ ```ruby
37
+ Thread.current.backend.submit(
38
+ [:send, sock, chunk_header(len)],
39
+ [:splice, file, sock, len]
40
+ )
41
+ ```
42
+
43
+ Full example (for writing chunks from a file to an HTTP response):
44
+
45
+ ```ruby
46
+ def serve_io(io)
47
+ i, o = IO.pipe
48
+ backend = Thread.current.backend
49
+ while true
50
+ len = o.splice(io, 8192)
51
+ break if len == 0
52
+
53
+ backend.submit(
54
+ [:write, sock, chunk_header(len)],
55
+ [:splice, i, sock, len]
56
+ )
57
+ end
58
+ end
59
+ ```
60
+
30
61
  - Graceful shutdown again:
31
62
  - What happens to children when doing a graceful shutdown?
32
63
  - What are the implications of passing graceful shutdown flag to children?
@@ -5,7 +5,7 @@ require 'polyphony'
5
5
 
6
6
  puts "parent pid: #{Process.pid}"
7
7
 
8
- pid = Polyphony.fork do
8
+ pid = fork do
9
9
  puts "child pid: #{Process.pid}"
10
10
 
11
11
  spin do
@@ -20,5 +20,5 @@ end
20
20
  puts "got child pid #{pid}"
21
21
 
22
22
  puts 'parent waiting for child'
23
- Thread.current.backend.waitpid(pid)
23
+ Polyphony.backend_waitpid(pid)
24
24
  puts 'parent done waiting'
@@ -9,6 +9,7 @@ puts 'Echoing on port 1234...'
9
9
  while (client = server.accept)
10
10
  spin do
11
11
  while (data = client.gets)
12
+ # client.send("you said: #{data.chomp}!\n", 0)
12
13
  client.write('you said: ', data.chomp, "!\n")
13
14
  end
14
15
  rescue Errno::ECONNRESET
@@ -15,7 +15,7 @@ f1 = spin {
15
15
  client1 = server1.accept
16
16
  loop do
17
17
  if client2
18
- Thread.current.backend.splice_loop(client1, client2)
18
+ Thread.current.backend.splice_to_eof(client1, client2)
19
19
  end
20
20
  end
21
21
  }
@@ -24,7 +24,7 @@ f2 = spin {
24
24
  client2 = server2.accept
25
25
  loop do
26
26
  if client1
27
- Thread.current.backend.splice_loop(client2, client1)
27
+ Thread.current.backend.splice_to_eof(client2, client1)
28
28
  end
29
29
  end
30
30
  }
@@ -3,6 +3,17 @@
3
3
  #include "ruby.h"
4
4
  #include "ruby/io.h"
5
5
 
6
+
7
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
8
+ #ifndef __NR_pidfd_open
9
+ #define __NR_pidfd_open 434 /* System call # on most architectures */
10
+ #endif
11
+
12
+ static int pidfd_open(pid_t pid, unsigned int flags) {
13
+ return syscall(__NR_pidfd_open, pid, flags);
14
+ }
15
+ #endif
16
+
6
17
  //////////////////////////////////////////////////////////////////////
7
18
  //////////////////////////////////////////////////////////////////////
8
19
  // the following is copied verbatim from the Ruby source code (io.c)
@@ -15,11 +26,11 @@ struct io_internal_read_struct {
15
26
 
16
27
  #define StringValue(v) rb_string_value(&(v))
17
28
 
18
- inline int io_setstrbuf(VALUE *str, long len) {
29
+ int io_setstrbuf(VALUE *str, long len) {
19
30
  #ifdef _WIN32
20
31
  len = (len + 1) & ~1L; /* round up for wide char */
21
32
  #endif
22
- if (NIL_P(*str)) {
33
+ if (*str == Qnil) {
23
34
  *str = rb_str_new(0, len);
24
35
  return 1;
25
36
  }
@@ -44,7 +55,7 @@ inline void io_shrink_read_string(VALUE str, long n) {
44
55
  }
45
56
  }
46
57
 
47
- inline void io_set_read_length(VALUE str, long n, int shrinkable) {
58
+ void io_set_read_length(VALUE str, long n, int shrinkable) {
48
59
  if (RSTRING_LEN(str) != n) {
49
60
  rb_str_modify(str);
50
61
  rb_str_set_len(str, n);
@@ -59,7 +70,7 @@ inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
59
70
  return rb_default_external_encoding();
60
71
  }
61
72
 
62
- inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
73
+ VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
63
74
  OBJ_TAINT(str);
64
75
  rb_enc_associate(str, io_read_encoding(fptr));
65
76
  return str;
@@ -99,6 +110,13 @@ inline VALUE backend_snooze() {
99
110
  READ_LOOP_PREPARE_STR(); \
100
111
  }
101
112
 
113
+ #define READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id) { \
114
+ io_set_read_length(str, total, shrinkable); \
115
+ io_enc_str(str, fptr); \
116
+ rb_funcall_passing_block(receiver, method_id, 1, &str); \
117
+ READ_LOOP_PREPARE_STR(); \
118
+ }
119
+
102
120
  inline void rectify_io_file_pos(rb_io_t *fptr) {
103
121
  // Apparently after reopening a closed file, the file position is not reset,
104
122
  // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
@@ -120,10 +138,42 @@ inline double current_time() {
120
138
  }
121
139
 
122
140
  inline VALUE backend_timeout_exception(VALUE exception) {
123
- if (RTEST(rb_obj_is_kind_of(exception, rb_cArray)))
141
+ if (rb_obj_is_kind_of(exception, rb_cArray) == Qtrue)
124
142
  return rb_funcall(rb_ary_entry(exception, 0), ID_new, 1, rb_ary_entry(exception, 1));
125
- else if (RTEST(rb_obj_is_kind_of(exception, rb_cClass)))
143
+ else if (rb_obj_is_kind_of(exception, rb_cClass) == Qtrue)
126
144
  return rb_funcall(exception, ID_new, 0);
127
145
  else
128
146
  return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
129
- }
147
+ }
148
+
149
+ VALUE Backend_timeout_safe(VALUE arg) {
150
+ return rb_yield(arg);
151
+ }
152
+
153
+ VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
154
+ return exception;
155
+ }
156
+
157
+ VALUE Backend_timeout_ensure_safe(VALUE arg) {
158
+ return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
159
+ }
160
+
161
+ static VALUE empty_string = Qnil;
162
+
163
+ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags) {
164
+ switch (RARRAY_LEN(ary)) {
165
+ case 0:
166
+ return Qnil;
167
+ case 1:
168
+ return Backend_send(self, io, RARRAY_AREF(ary, 0), flags);
169
+ default:
170
+ if (empty_string == Qnil) {
171
+ empty_string = rb_str_new_literal("");
172
+ rb_global_variable(&empty_string);
173
+ }
174
+ VALUE joined = rb_ary_join(ary, empty_string);
175
+ VALUE result = Backend_send(self, io, joined, flags);
176
+ RB_GC_GUARD(joined);
177
+ return result;
178
+ }
179
+ }
@@ -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
@@ -132,7 +150,7 @@ static inline bool cq_ring_needs_flush(struct io_uring *ring) {
132
150
  return IO_URING_READ_ONCE(*ring->sq.kflags) & IORING_SQ_CQ_OVERFLOW;
133
151
  }
134
152
 
135
- void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *backend) {
153
+ static inline void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *backend) {
136
154
  op_context_t *ctx = io_uring_cqe_get_data(cqe);
137
155
  if (!ctx) return;
138
156
 
@@ -151,7 +169,7 @@ void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *bac
151
169
  }
152
170
 
153
171
  // adapted from io_uring_peek_batch_cqe in queue.c
154
- // this peeks at cqes and for each one
172
+ // this peeks at cqes and handles each available cqe
155
173
  void io_uring_backend_handle_ready_cqes(Backend_t *backend) {
156
174
  struct io_uring *ring = &backend->ring;
157
175
  bool overflow_checked = false;
@@ -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);
@@ -297,8 +315,8 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
297
315
  Backend_t *backend;
298
316
  rb_io_t *fptr;
299
317
  long dynamic_len = length == Qnil;
300
- long len = dynamic_len ? 4096 : NUM2INT(length);
301
- int shrinkable = io_setstrbuf(&str, len);
318
+ long buffer_size = dynamic_len ? 4096 : NUM2INT(length);
319
+ int shrinkable = io_setstrbuf(&str, buffer_size);
302
320
  char *buf = RSTRING_PTR(str);
303
321
  long total = 0;
304
322
  int read_to_eof = RTEST(to_eof);
@@ -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
 
@@ -315,7 +334,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
315
334
  VALUE resume_value = Qnil;
316
335
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
317
336
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
318
- io_uring_prep_read(sqe, fptr->fd, buf, len - total, -1);
337
+ io_uring_prep_read(sqe, fptr->fd, buf, buffer_size - total, -1);
319
338
 
320
339
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
321
340
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -331,14 +350,15 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
331
350
  total += result;
332
351
  if (!read_to_eof) break;
333
352
 
334
- if (total == len) {
353
+ if (total == buffer_size) {
335
354
  if (!dynamic_len) break;
336
355
 
356
+ // resize buffer
337
357
  rb_str_resize(str, total);
338
- rb_str_modify_expand(str, len);
358
+ rb_str_modify_expand(str, buffer_size);
339
359
  buf = RSTRING_PTR(str) + total;
340
360
  shrinkable = 0;
341
- len += len;
361
+ buffer_size += buffer_size;
342
362
  }
343
363
  else buf += result;
344
364
  }
@@ -368,6 +388,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
368
388
  if (underlying_io != Qnil) io = underlying_io;
369
389
  GetOpenFile(io, fptr);
370
390
  rb_io_check_byte_readable(fptr);
391
+ io_unset_nonblock(fptr, io);
371
392
  rectify_io_file_pos(fptr);
372
393
 
373
394
  while (1) {
@@ -397,6 +418,53 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
397
418
  return io;
398
419
  }
399
420
 
421
+ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
422
+ Backend_t *backend;
423
+ rb_io_t *fptr;
424
+ VALUE str;
425
+ long total;
426
+ long len = 8192;
427
+ int shrinkable;
428
+ char *buf;
429
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
430
+ ID method_id = SYM2ID(method);
431
+
432
+ READ_LOOP_PREPARE_STR();
433
+
434
+ GetBackend(self, backend);
435
+ if (underlying_io != Qnil) io = underlying_io;
436
+ GetOpenFile(io, fptr);
437
+ rb_io_check_byte_readable(fptr);
438
+ io_unset_nonblock(fptr, io);
439
+ rectify_io_file_pos(fptr);
440
+
441
+ while (1) {
442
+ VALUE resume_value = Qnil;
443
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
444
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
445
+ io_uring_prep_read(sqe, fptr->fd, buf, len, -1);
446
+
447
+ ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
448
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
449
+ RAISE_IF_EXCEPTION(resume_value);
450
+ if (!ctx->completed) return resume_value;
451
+ RB_GC_GUARD(resume_value);
452
+
453
+ if (result < 0)
454
+ rb_syserr_fail(-result, strerror(-result));
455
+ else if (!result)
456
+ break; // EOF
457
+ else {
458
+ total = result;
459
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
460
+ }
461
+ }
462
+
463
+ RB_GC_GUARD(str);
464
+
465
+ return io;
466
+ }
467
+
400
468
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
401
469
  Backend_t *backend;
402
470
  rb_io_t *fptr;
@@ -407,6 +475,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
407
475
  GetBackend(self, backend);
408
476
  io = rb_io_get_write_io(io);
409
477
  GetOpenFile(io, fptr);
478
+ io_unset_nonblock(fptr, io);
410
479
 
411
480
  char *buf = StringValuePtr(str);
412
481
  long len = RSTRING_LEN(str);
@@ -450,6 +519,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
450
519
  GetBackend(self, backend);
451
520
  io = rb_io_get_write_io(io);
452
521
  GetOpenFile(io, fptr);
522
+ io_unset_nonblock(fptr, io);
453
523
 
454
524
  iov = malloc(iov_count * sizeof(struct iovec));
455
525
  for (int i = 0; i < argc; i++) {
@@ -529,6 +599,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
529
599
  if (underlying_io != Qnil) io = underlying_io;
530
600
  GetOpenFile(io, fptr);
531
601
  rb_io_check_byte_readable(fptr);
602
+ io_unset_nonblock(fptr, io);
532
603
  rectify_io_file_pos(fptr);
533
604
  OBJ_TAINT(str);
534
605
 
@@ -576,6 +647,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
576
647
  if (underlying_io != Qnil) io = underlying_io;
577
648
  GetOpenFile(io, fptr);
578
649
  rb_io_check_byte_readable(fptr);
650
+ io_unset_nonblock(fptr, io);
579
651
  rectify_io_file_pos(fptr);
580
652
 
581
653
  while (1) {
@@ -604,7 +676,53 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
604
676
  return io;
605
677
  }
606
678
 
607
- VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
679
+ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
680
+ Backend_t *backend;
681
+ rb_io_t *fptr;
682
+ VALUE str;
683
+ long total;
684
+ long len = 8192;
685
+ int shrinkable;
686
+ char *buf;
687
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
688
+ ID method_id = SYM2ID(method);
689
+
690
+ READ_LOOP_PREPARE_STR();
691
+
692
+ GetBackend(self, backend);
693
+ if (underlying_io != Qnil) io = underlying_io;
694
+ GetOpenFile(io, fptr);
695
+ rb_io_check_byte_readable(fptr);
696
+ io_unset_nonblock(fptr, io);
697
+ rectify_io_file_pos(fptr);
698
+
699
+ while (1) {
700
+ VALUE resume_value = Qnil;
701
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_RECV);
702
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
703
+ io_uring_prep_recv(sqe, fptr->fd, buf, len, 0);
704
+
705
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
706
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
707
+ RAISE_IF_EXCEPTION(resume_value);
708
+ if (!ctx->completed) return resume_value;
709
+ RB_GC_GUARD(resume_value);
710
+
711
+ if (result < 0)
712
+ rb_syserr_fail(-result, strerror(-result));
713
+ else if (!result)
714
+ break; // EOF
715
+ else {
716
+ total = result;
717
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
718
+ }
719
+ }
720
+
721
+ RB_GC_GUARD(str);
722
+ return io;
723
+ }
724
+
725
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
608
726
  Backend_t *backend;
609
727
  rb_io_t *fptr;
610
728
  VALUE underlying_io;
@@ -614,16 +732,18 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
614
732
  GetBackend(self, backend);
615
733
  io = rb_io_get_write_io(io);
616
734
  GetOpenFile(io, fptr);
735
+ io_unset_nonblock(fptr, io);
617
736
 
618
737
  char *buf = StringValuePtr(str);
619
738
  long len = RSTRING_LEN(str);
620
739
  long left = len;
740
+ int flags_int = NUM2INT(flags);
621
741
 
622
742
  while (left > 0) {
623
743
  VALUE resume_value = Qnil;
624
744
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SEND);
625
745
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
626
- io_uring_prep_send(sqe, fptr->fd, buf, left, 0);
746
+ io_uring_prep_send(sqe, fptr->fd, buf, left, flags_int);
627
747
 
628
748
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
629
749
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -651,6 +771,8 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE soc
651
771
  if (underlying_sock != Qnil) server_socket = underlying_sock;
652
772
 
653
773
  GetOpenFile(server_socket, fptr);
774
+ io_unset_nonblock(fptr, server_socket);
775
+
654
776
  while (1) {
655
777
  VALUE resume_value = Qnil;
656
778
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
@@ -704,6 +826,60 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
704
826
  return self;
705
827
  }
706
828
 
829
+ VALUE io_uring_backend_splice(Backend_t *backend, VALUE src, VALUE dest, VALUE maxlen, int loop) {
830
+ rb_io_t *src_fptr;
831
+ rb_io_t *dest_fptr;
832
+ VALUE underlying_io;
833
+ int total = 0;
834
+
835
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
836
+ if (underlying_io != Qnil) src = underlying_io;
837
+ GetOpenFile(src, src_fptr);
838
+ io_unset_nonblock(src_fptr, src);
839
+
840
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
841
+ if (underlying_io != Qnil) dest = underlying_io;
842
+ dest = rb_io_get_write_io(dest);
843
+ GetOpenFile(dest, dest_fptr);
844
+ io_unset_nonblock(dest_fptr, dest);
845
+
846
+ VALUE resume_value = Qnil;
847
+
848
+ while (1) {
849
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SPLICE);
850
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
851
+ io_uring_prep_splice(sqe, src_fptr->fd, -1, dest_fptr->fd, -1, NUM2INT(maxlen), 0);
852
+
853
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
854
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
855
+ RAISE_IF_EXCEPTION(resume_value);
856
+ if (!ctx->completed) return resume_value;
857
+
858
+ if (result < 0)
859
+ rb_syserr_fail(-result, strerror(-result));
860
+
861
+ total += result;
862
+ if (result == 0 || !loop) return INT2NUM(total);
863
+ }
864
+
865
+ RB_GC_GUARD(resume_value);
866
+ }
867
+
868
+ VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
869
+ Backend_t *backend;
870
+ GetBackend(self, backend);
871
+
872
+ return io_uring_backend_splice(backend, src, dest, maxlen, 0);
873
+ }
874
+
875
+ VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE chunksize) {
876
+ Backend_t *backend;
877
+ GetBackend(self, backend);
878
+
879
+ return io_uring_backend_splice(backend, src, dest, chunksize, 1);
880
+ }
881
+
882
+
707
883
  VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
708
884
  Backend_t *backend;
709
885
  rb_io_t *fptr;
@@ -714,6 +890,7 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
714
890
 
715
891
  GetBackend(self, backend);
716
892
  GetOpenFile(sock, fptr);
893
+ io_unset_nonblock(fptr, sock);
717
894
 
718
895
  addr.sin_family = AF_INET;
719
896
  addr.sin_addr.s_addr = inet_addr(host_buf);
@@ -740,6 +917,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
740
917
  if (underlying_io != Qnil) io = underlying_io;
741
918
  GetBackend(self, backend);
742
919
  GetOpenFile(io, fptr);
920
+ io_unset_nonblock(fptr, io);
743
921
 
744
922
  VALUE resume_value = io_uring_backend_wait_fd(backend, fptr->fd, RTEST(write));
745
923
  RAISE_IF_EXCEPTION(resume_value);
@@ -811,18 +989,6 @@ VALUE Backend_timer_loop(VALUE self, VALUE interval) {
811
989
  }
812
990
  }
813
991
 
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
992
  struct Backend_timeout_ctx {
827
993
  Backend_t *backend;
828
994
  op_context_t *ctx;
@@ -861,7 +1027,6 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
861
1027
  ctx->resume_value = timeout;
862
1028
  io_uring_prep_timeout(sqe, &ts, 0, 0);
863
1029
  io_uring_sqe_set_data(sqe, ctx);
864
- io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
865
1030
  io_uring_backend_defer_submit(backend);
866
1031
 
867
1032
  struct Backend_timeout_ctx timeout_ctx = {backend, ctx};
@@ -879,19 +1044,28 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
879
1044
  }
880
1045
 
881
1046
  VALUE Backend_waitpid(VALUE self, VALUE pid) {
882
- Backend_t *backend;
883
1047
  int pid_int = NUM2INT(pid);
884
1048
  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
1049
 
890
- RAISE_IF_EXCEPTION(resume_value);
891
- RB_GC_GUARD(resume_value);
1050
+ if (fd >= 0) {
1051
+ Backend_t *backend;
1052
+ GetBackend(self, backend);
892
1053
 
1054
+ VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
1055
+ close(fd);
1056
+ RAISE_IF_EXCEPTION(resume_value);
1057
+ RB_GC_GUARD(resume_value);
1058
+ }
1059
+
893
1060
  int status;
894
1061
  pid_t ret = waitpid(pid_int, &status, WNOHANG);
1062
+ if (ret < 0) {
1063
+ int e = errno;
1064
+ if (e == ECHILD)
1065
+ ret = pid_int;
1066
+ else
1067
+ rb_syserr_fail(e, strerror(e));
1068
+ }
895
1069
  return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
896
1070
  }
897
1071
 
@@ -918,7 +1092,7 @@ VALUE Backend_kind(VALUE self) {
918
1092
  }
919
1093
 
920
1094
  void Init_Backend() {
921
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1095
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
922
1096
  rb_define_alloc_func(cBackend, Backend_allocate);
923
1097
 
924
1098
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -927,24 +1101,33 @@ void Init_Backend() {
927
1101
 
928
1102
  rb_define_method(cBackend, "poll", Backend_poll, 3);
929
1103
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1104
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
930
1105
 
1106
+ rb_define_method(cBackend, "accept", Backend_accept, 2);
1107
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
1108
+ rb_define_method(cBackend, "connect", Backend_connect, 3);
1109
+ rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
931
1110
  rb_define_method(cBackend, "read", Backend_read, 4);
932
1111
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
933
- rb_define_method(cBackend, "write", Backend_write_m, -1);
934
1112
  rb_define_method(cBackend, "recv", Backend_recv, 3);
1113
+ rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
935
1114
  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);
1115
+ rb_define_method(cBackend, "send", Backend_send, 3);
1116
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
941
1117
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
942
- rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
1118
+ rb_define_method(cBackend, "splice", Backend_splice, 3);
1119
+ rb_define_method(cBackend, "splice_to_eof", Backend_splice_to_eof, 3);
943
1120
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
944
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1121
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
945
1122
  rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
1123
+ rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1124
+ rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1125
+ rb_define_method(cBackend, "write", Backend_write_m, -1);
946
1126
 
947
- rb_define_method(cBackend, "kind", Backend_kind, 0);
1127
+
1128
+ #ifdef POLYPHONY_UNSET_NONBLOCK
1129
+ ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
1130
+ #endif
948
1131
 
949
1132
  SYM_io_uring = ID2SYM(rb_intern("io_uring"));
950
1133
  }