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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 126431d6f1b6c547d57dd99834c5a2371b4f2b3399df5556f55fdf7ff8465702
4
- data.tar.gz: bf4e8d8cbde0a0d25930996f21ca723ffb68c065688ea2423f96b2a1e8946a80
3
+ metadata.gz: d656c0aad868a468632949355b617821b6a66aa4da69a994ea7afbc4af46e97b
4
+ data.tar.gz: edc45b184b0b50940925a1a1db56b0106ded10906ca181513e38450bc320e828
5
5
  SHA512:
6
- metadata.gz: 9b6cb2b64ac16e70941195aa17c019abdb026bac2d440550841f839bae96d248b87609979e6e9d7c57f9905845209714d90f74caa4488d8ba66afa96c35aa425
7
- data.tar.gz: '093360cb9c3f5e81d7d23f1e707bf6399608475822b56d0171438748e65752adb2784b1ab36ce850a26840c615f73ce1fcc74ca842018361019269f7d3c31aa9'
6
+ metadata.gz: f346672c82eee4055ec6c8f7fc1103850fd622a031ebf1c096e9ab130e3aa0b37c559d2b2f953f6b1bd721e4421199800fc0de54993a0c802ddadfa873f35030
7
+ data.tar.gz: 8273d691c2ed19ca04207380ea99392c7f65fefe6600c4a9bf9cc20a802f41e34b471e3af8d5adccf29c783c2f0c17610896fae2c183ac26b601c34dc27a5ce2
@@ -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,26 @@
1
+ ## 0.53.2
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
+
1
24
  ## 0.50.1
2
25
 
3
26
  - Set `IOSQE_ASYNC` flag in io_uring backend
data/Gemfile.lock CHANGED
@@ -1,101 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.50.1)
4
+ polyphony (0.53.2)
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,8 @@
3
3
  #include "ruby.h"
4
4
  #include "ruby/io.h"
5
5
 
6
+
7
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
6
8
  #ifndef __NR_pidfd_open
7
9
  #define __NR_pidfd_open 434 /* System call # on most architectures */
8
10
  #endif
@@ -10,6 +12,7 @@
10
12
  static int pidfd_open(pid_t pid, unsigned int flags) {
11
13
  return syscall(__NR_pidfd_open, pid, flags);
12
14
  }
15
+ #endif
13
16
 
14
17
  //////////////////////////////////////////////////////////////////////
15
18
  //////////////////////////////////////////////////////////////////////
@@ -23,11 +26,11 @@ struct io_internal_read_struct {
23
26
 
24
27
  #define StringValue(v) rb_string_value(&(v))
25
28
 
26
- inline int io_setstrbuf(VALUE *str, long len) {
29
+ int io_setstrbuf(VALUE *str, long len) {
27
30
  #ifdef _WIN32
28
31
  len = (len + 1) & ~1L; /* round up for wide char */
29
32
  #endif
30
- if (NIL_P(*str)) {
33
+ if (*str == Qnil) {
31
34
  *str = rb_str_new(0, len);
32
35
  return 1;
33
36
  }
@@ -52,7 +55,7 @@ inline void io_shrink_read_string(VALUE str, long n) {
52
55
  }
53
56
  }
54
57
 
55
- inline void io_set_read_length(VALUE str, long n, int shrinkable) {
58
+ void io_set_read_length(VALUE str, long n, int shrinkable) {
56
59
  if (RSTRING_LEN(str) != n) {
57
60
  rb_str_modify(str);
58
61
  rb_str_set_len(str, n);
@@ -67,7 +70,7 @@ inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
67
70
  return rb_default_external_encoding();
68
71
  }
69
72
 
70
- inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
73
+ VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
71
74
  OBJ_TAINT(str);
72
75
  rb_enc_associate(str, io_read_encoding(fptr));
73
76
  return str;
@@ -107,6 +110,13 @@ inline VALUE backend_snooze() {
107
110
  READ_LOOP_PREPARE_STR(); \
108
111
  }
109
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
+
110
120
  inline void rectify_io_file_pos(rb_io_t *fptr) {
111
121
  // Apparently after reopening a closed file, the file position is not reset,
112
122
  // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
@@ -128,9 +138,9 @@ inline double current_time() {
128
138
  }
129
139
 
130
140
  inline VALUE backend_timeout_exception(VALUE exception) {
131
- if (RTEST(rb_obj_is_kind_of(exception, rb_cArray)))
141
+ if (rb_obj_is_kind_of(exception, rb_cArray) == Qtrue)
132
142
  return rb_funcall(rb_ary_entry(exception, 0), ID_new, 1, rb_ary_entry(exception, 1));
133
- else if (RTEST(rb_obj_is_kind_of(exception, rb_cClass)))
143
+ else if (rb_obj_is_kind_of(exception, rb_cClass) == Qtrue)
134
144
  return rb_funcall(exception, ID_new, 0);
135
145
  else
136
146
  return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
@@ -147,3 +157,23 @@ VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
147
157
  VALUE Backend_timeout_ensure_safe(VALUE arg) {
148
158
  return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
149
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,11 +16,37 @@
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
23
  VALUE SYM_io_uring;
23
24
 
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);
44
+ }
45
+ #else
46
+ // NOP
47
+ #define io_unset_nonblock(fptr, io)
48
+ #endif
49
+
24
50
  typedef struct Backend_t {
25
51
  // common fields
26
52
  unsigned int currently_polling;
@@ -124,7 +150,7 @@ static inline bool cq_ring_needs_flush(struct io_uring *ring) {
124
150
  return IO_URING_READ_ONCE(*ring->sq.kflags) & IORING_SQ_CQ_OVERFLOW;
125
151
  }
126
152
 
127
- 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) {
128
154
  op_context_t *ctx = io_uring_cqe_get_data(cqe);
129
155
  if (!ctx) return;
130
156
 
@@ -143,7 +169,7 @@ void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *bac
143
169
  }
144
170
 
145
171
  // adapted from io_uring_peek_batch_cqe in queue.c
146
- // this peeks at cqes and for each one
172
+ // this peeks at cqes and handles each available cqe
147
173
  void io_uring_backend_handle_ready_cqes(Backend_t *backend) {
148
174
  struct io_uring *ring = &backend->ring;
149
175
  bool overflow_checked = false;
@@ -289,8 +315,8 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
289
315
  Backend_t *backend;
290
316
  rb_io_t *fptr;
291
317
  long dynamic_len = length == Qnil;
292
- long len = dynamic_len ? 4096 : NUM2INT(length);
293
- int shrinkable = io_setstrbuf(&str, len);
318
+ long buffer_size = dynamic_len ? 4096 : NUM2INT(length);
319
+ int shrinkable = io_setstrbuf(&str, buffer_size);
294
320
  char *buf = RSTRING_PTR(str);
295
321
  long total = 0;
296
322
  int read_to_eof = RTEST(to_eof);
@@ -300,6 +326,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
300
326
  if (underlying_io != Qnil) io = underlying_io;
301
327
  GetOpenFile(io, fptr);
302
328
  rb_io_check_byte_readable(fptr);
329
+ io_unset_nonblock(fptr, io);
303
330
  rectify_io_file_pos(fptr);
304
331
  OBJ_TAINT(str);
305
332
 
@@ -307,7 +334,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
307
334
  VALUE resume_value = Qnil;
308
335
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
309
336
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
310
- io_uring_prep_read(sqe, fptr->fd, buf, len - total, -1);
337
+ io_uring_prep_read(sqe, fptr->fd, buf, buffer_size - total, -1);
311
338
 
312
339
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
313
340
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -323,14 +350,15 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
323
350
  total += result;
324
351
  if (!read_to_eof) break;
325
352
 
326
- if (total == len) {
353
+ if (total == buffer_size) {
327
354
  if (!dynamic_len) break;
328
355
 
356
+ // resize buffer
329
357
  rb_str_resize(str, total);
330
- rb_str_modify_expand(str, len);
358
+ rb_str_modify_expand(str, buffer_size);
331
359
  buf = RSTRING_PTR(str) + total;
332
360
  shrinkable = 0;
333
- len += len;
361
+ buffer_size += buffer_size;
334
362
  }
335
363
  else buf += result;
336
364
  }
@@ -360,6 +388,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
360
388
  if (underlying_io != Qnil) io = underlying_io;
361
389
  GetOpenFile(io, fptr);
362
390
  rb_io_check_byte_readable(fptr);
391
+ io_unset_nonblock(fptr, io);
363
392
  rectify_io_file_pos(fptr);
364
393
 
365
394
  while (1) {
@@ -389,6 +418,53 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
389
418
  return io;
390
419
  }
391
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
+
392
468
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
393
469
  Backend_t *backend;
394
470
  rb_io_t *fptr;
@@ -399,6 +475,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
399
475
  GetBackend(self, backend);
400
476
  io = rb_io_get_write_io(io);
401
477
  GetOpenFile(io, fptr);
478
+ io_unset_nonblock(fptr, io);
402
479
 
403
480
  char *buf = StringValuePtr(str);
404
481
  long len = RSTRING_LEN(str);
@@ -442,6 +519,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
442
519
  GetBackend(self, backend);
443
520
  io = rb_io_get_write_io(io);
444
521
  GetOpenFile(io, fptr);
522
+ io_unset_nonblock(fptr, io);
445
523
 
446
524
  iov = malloc(iov_count * sizeof(struct iovec));
447
525
  for (int i = 0; i < argc; i++) {
@@ -521,6 +599,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
521
599
  if (underlying_io != Qnil) io = underlying_io;
522
600
  GetOpenFile(io, fptr);
523
601
  rb_io_check_byte_readable(fptr);
602
+ io_unset_nonblock(fptr, io);
524
603
  rectify_io_file_pos(fptr);
525
604
  OBJ_TAINT(str);
526
605
 
@@ -568,6 +647,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
568
647
  if (underlying_io != Qnil) io = underlying_io;
569
648
  GetOpenFile(io, fptr);
570
649
  rb_io_check_byte_readable(fptr);
650
+ io_unset_nonblock(fptr, io);
571
651
  rectify_io_file_pos(fptr);
572
652
 
573
653
  while (1) {
@@ -596,7 +676,53 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
596
676
  return io;
597
677
  }
598
678
 
599
- 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) {
600
726
  Backend_t *backend;
601
727
  rb_io_t *fptr;
602
728
  VALUE underlying_io;
@@ -606,16 +732,18 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
606
732
  GetBackend(self, backend);
607
733
  io = rb_io_get_write_io(io);
608
734
  GetOpenFile(io, fptr);
735
+ io_unset_nonblock(fptr, io);
609
736
 
610
737
  char *buf = StringValuePtr(str);
611
738
  long len = RSTRING_LEN(str);
612
739
  long left = len;
740
+ int flags_int = NUM2INT(flags);
613
741
 
614
742
  while (left > 0) {
615
743
  VALUE resume_value = Qnil;
616
744
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SEND);
617
745
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
618
- io_uring_prep_send(sqe, fptr->fd, buf, left, 0);
746
+ io_uring_prep_send(sqe, fptr->fd, buf, left, flags_int);
619
747
 
620
748
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
621
749
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -643,6 +771,8 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE soc
643
771
  if (underlying_sock != Qnil) server_socket = underlying_sock;
644
772
 
645
773
  GetOpenFile(server_socket, fptr);
774
+ io_unset_nonblock(fptr, server_socket);
775
+
646
776
  while (1) {
647
777
  VALUE resume_value = Qnil;
648
778
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
@@ -696,6 +826,60 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
696
826
  return self;
697
827
  }
698
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
+
699
883
  VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
700
884
  Backend_t *backend;
701
885
  rb_io_t *fptr;
@@ -706,6 +890,7 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
706
890
 
707
891
  GetBackend(self, backend);
708
892
  GetOpenFile(sock, fptr);
893
+ io_unset_nonblock(fptr, sock);
709
894
 
710
895
  addr.sin_family = AF_INET;
711
896
  addr.sin_addr.s_addr = inet_addr(host_buf);
@@ -732,6 +917,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
732
917
  if (underlying_io != Qnil) io = underlying_io;
733
918
  GetBackend(self, backend);
734
919
  GetOpenFile(io, fptr);
920
+ io_unset_nonblock(fptr, io);
735
921
 
736
922
  VALUE resume_value = io_uring_backend_wait_fd(backend, fptr->fd, RTEST(write));
737
923
  RAISE_IF_EXCEPTION(resume_value);
@@ -906,7 +1092,7 @@ VALUE Backend_kind(VALUE self) {
906
1092
  }
907
1093
 
908
1094
  void Init_Backend() {
909
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1095
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
910
1096
  rb_define_alloc_func(cBackend, Backend_allocate);
911
1097
 
912
1098
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -915,24 +1101,33 @@ void Init_Backend() {
915
1101
 
916
1102
  rb_define_method(cBackend, "poll", Backend_poll, 3);
917
1103
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1104
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
918
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);
919
1110
  rb_define_method(cBackend, "read", Backend_read, 4);
920
1111
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
921
- rb_define_method(cBackend, "write", Backend_write_m, -1);
922
1112
  rb_define_method(cBackend, "recv", Backend_recv, 3);
1113
+ rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
923
1114
  rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
924
- rb_define_method(cBackend, "send", Backend_send, 2);
925
- rb_define_method(cBackend, "accept", Backend_accept, 2);
926
- rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
927
- rb_define_method(cBackend, "connect", Backend_connect, 3);
928
- 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);
929
1117
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
930
- 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);
931
1120
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
932
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1121
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
933
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);
934
1126
 
935
- 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
936
1131
 
937
1132
  SYM_io_uring = ID2SYM(rb_intern("io_uring"));
938
1133
  }