polyphony 0.51.0 → 0.54.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -2
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +37 -6
  6. data/examples/core/forking.rb +2 -2
  7. data/examples/core/queue.rb +19 -0
  8. data/examples/io/echo_server.rb +1 -0
  9. data/examples/io/https_server.rb +30 -0
  10. data/examples/io/tcp_proxy.rb +2 -2
  11. data/ext/polyphony/backend_common.h +29 -6
  12. data/ext/polyphony/backend_io_uring.c +125 -23
  13. data/ext/polyphony/backend_io_uring_context.c +1 -0
  14. data/ext/polyphony/backend_io_uring_context.h +1 -0
  15. data/ext/polyphony/backend_libev.c +309 -21
  16. data/ext/polyphony/event.c +1 -1
  17. data/ext/polyphony/extconf.rb +9 -2
  18. data/ext/polyphony/polyphony.c +102 -0
  19. data/ext/polyphony/polyphony.h +32 -2
  20. data/ext/polyphony/polyphony_ext.c +3 -0
  21. data/ext/polyphony/queue.c +1 -1
  22. data/ext/polyphony/runqueue.c +1 -1
  23. data/ext/polyphony/socket_extensions.c +33 -0
  24. data/ext/polyphony/thread.c +8 -2
  25. data/lib/polyphony/adapters/irb.rb +1 -1
  26. data/lib/polyphony/adapters/mysql2.rb +1 -1
  27. data/lib/polyphony/adapters/postgres.rb +5 -5
  28. data/lib/polyphony/adapters/process.rb +2 -2
  29. data/lib/polyphony/core/global_api.rb +5 -5
  30. data/lib/polyphony/core/sync.rb +9 -1
  31. data/lib/polyphony/core/throttler.rb +1 -1
  32. data/lib/polyphony/core/timer.rb +2 -2
  33. data/lib/polyphony/extensions/core.rb +1 -1
  34. data/lib/polyphony/extensions/io.rb +20 -25
  35. data/lib/polyphony/extensions/openssl.rb +28 -21
  36. data/lib/polyphony/extensions/socket.rb +51 -54
  37. data/lib/polyphony/version.rb +1 -1
  38. data/polyphony.gemspec +6 -5
  39. data/test/helper.rb +1 -1
  40. data/test/stress.rb +2 -0
  41. data/test/test_backend.rb +152 -5
  42. data/test/test_global_api.rb +2 -2
  43. data/test/test_io.rb +33 -2
  44. data/test/test_kernel.rb +1 -1
  45. data/test/test_signal.rb +1 -1
  46. data/test/test_socket.rb +27 -0
  47. data/test/test_sync.rb +43 -0
  48. data/test/test_thread.rb +4 -0
  49. data/test/test_timer.rb +1 -1
  50. metadata +10 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72f11d867863c51f1fc1ab3ec85fe575cca9ee53a5047f4d3f7b76cea3b650b7
4
- data.tar.gz: c29354b2de2f207185fc06fd86421b53d328aaf67d611109a6f95e9130ac5967
3
+ metadata.gz: 7c88e804739b62a55f7647c57e55582d2ae5cf6a73bca55160aa90138b80217a
4
+ data.tar.gz: ecbeb0aeed96a7b6f21f8d41b67195574213b8b01b51bccd0f1bcf59165ab9f6
5
5
  SHA512:
6
- metadata.gz: 93351a3c4007145ff317257f82b764708b445e94b848d31f783f4d72071b097a8309322c4bedfdb7f321b0613ef436a314ad4e650928d0855143d3a5b98d9c13
7
- data.tar.gz: 9119039548264867fa012bcbb90999f4ad82170c28539d43b0145687a761d508630da538ac87b75f9bf32f6b33cb0e13c22f50143248b85cc90289d5d3451d3f
6
+ metadata.gz: 93ff227cab8891c79bc4447c25359df03cd675ada579d13c5dc09d1f18e42fd00ed1b3ce5e433bd5addf83076af3a45c29cdd49ae8cb2cb9f19892cb27a40476
7
+ data.tar.gz: 1e1f39c19d2edbd49406c3a3917848951914c48b81316fff6ff78f381fb7db70b407dc5428b94a45292261e202df1c91da5d4e971ab19ba91e7c96e0bbbb91bc
@@ -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}}
@@ -26,6 +26,6 @@ jobs:
26
26
  - name: Show Linux kernel version
27
27
  run: uname -r
28
28
  - name: Compile C-extension
29
- run: bundle exec rake compile
29
+ run: POLYPHONY_USE_LIBEV=1 bundle exec rake compile
30
30
  - name: Run tests
31
31
  run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 0.54.0
2
+
3
+ - Implement Mutex#owned?, #locked? (#50)
4
+ - Fix arity for SSLSocket#peeraddr (#55)
5
+ - Add missing SSLServer#accept_loop method (#53)
6
+ - Fix SSLSocket buffering behaviour
7
+ - Add recv_loop alias for SSLSocket (#54)
8
+
9
+ ## 0.53.2
10
+
11
+ - Remove `splice` methods on libev backend on non-Linux OS (#43)
12
+
13
+ ## 0.53.0
14
+
15
+ - Implement `Backend#splice`, `Backend#splice_to_eof`, along with `IO#splice`, `IO#splice_to_eof`
16
+
17
+ ## 0.52.0
18
+
19
+ - Polyphony is now compatible with Ruby 3.0
20
+ - Add `Backend#sendv` method for sending multiple strings
21
+ - Accept flags argument in `Backend#send` (#48)
22
+ - Fix io_uring backend on Ruby 3.0 (#47)
23
+ - Implement C-based public backend API: `Polyphony.backend_XXXX` methods
24
+ - libev backend: Use` pidfd_open` for Linux 5.3+, otherwise use a libev child watcher
25
+ - Use `:call` as default method in `#feed_loop`
26
+
1
27
  ## 0.51.0
2
28
 
3
29
  - Implement `IO#feed_loop`, `Socket#feed_loop`
data/Gemfile.lock CHANGED
@@ -1,72 +1,28 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.51.0)
4
+ polyphony (0.54.0)
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)
20
+ localhost (1.1.8)
65
21
  method_source (1.0.0)
66
22
  mime-types (3.3.1)
67
23
  mime-types-data (~> 3.2015)
68
24
  mime-types-data (3.2020.0512)
69
- minitest (5.13.0)
25
+ minitest (5.14.4)
70
26
  minitest-reporters (1.4.2)
71
27
  ansi
72
28
  builder
@@ -78,25 +34,18 @@ GEM
78
34
  parallel (1.19.1)
79
35
  parser (2.7.0.2)
80
36
  ast (~> 2.4.0)
81
- pathutil (0.16.2)
82
- forwardable-extended (~> 2.6)
83
37
  pg (1.1.4)
84
38
  pry (0.13.1)
85
39
  coderay (~> 1.1)
86
40
  method_source (~> 1.0)
87
- public_suffix (4.0.3)
88
41
  rack (2.2.3)
89
42
  rainbow (3.0.0)
90
- rake (12.3.3)
43
+ rake (13.0.3)
91
44
  rake-compiler (1.1.1)
92
45
  rake
93
- rb-fsevent (0.10.3)
94
- rb-inotify (0.10.1)
95
- ffi (~> 1.0)
96
46
  redis (4.1.0)
97
47
  regexp_parser (1.7.1)
98
48
  rexml (3.2.4)
99
- rouge (3.15.0)
100
49
  rubocop (0.85.1)
101
50
  parallel (~> 1.10)
102
51
  parser (>= 2.7.0.1)
@@ -109,13 +58,6 @@ GEM
109
58
  rubocop-ast (0.0.3)
110
59
  parser (>= 2.7.0.1)
111
60
  ruby-progressbar (1.10.1)
112
- rubyzip (2.0.0)
113
- safe_yaml (1.0.5)
114
- sass (3.7.4)
115
- sass-listen (~> 4.0.0)
116
- sass-listen (4.0.0)
117
- rb-fsevent (~> 0.9, >= 0.9.4)
118
- rb-inotify (~> 0.9, >= 0.9.7)
119
61
  sequel (5.34.0)
120
62
  simplecov (0.17.1)
121
63
  docile (~> 1.1)
@@ -131,11 +73,8 @@ DEPENDENCIES
131
73
  hiredis (= 0.6.3)
132
74
  http_parser.rb (~> 0.6.0)
133
75
  httparty (= 0.17.1)
134
- jekyll (~> 3.8.6)
135
- jekyll-remote-theme (~> 0.4.1)
136
- jekyll-seo-tag (~> 2.6.1)
137
- just-the-docs (~> 0.3.0)
138
- minitest (= 5.13.0)
76
+ localhost (~> 1.1.4)
77
+ minitest (= 5.14.4)
139
78
  minitest-reporters (= 1.4.2)
140
79
  msgpack (= 1.4.2)
141
80
  mysql2 (= 0.5.3)
@@ -150,4 +89,4 @@ DEPENDENCIES
150
89
  simplecov (= 0.17.1)
151
90
 
152
91
  BUNDLED WITH
153
- 2.1.4
92
+ 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'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ queue = Queue.new
7
+
8
+ 4.times { |i|
9
+ spin_loop {
10
+ job = queue.pop
11
+ puts("worker %d job %s" % [i, job.inspect])
12
+ }
13
+ }
14
+
15
+ (1..10).each do |i|
16
+ queue << "job#{i}"
17
+ end
18
+
19
+ sleep 0.1 until queue.empty?
@@ -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
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'localhost/authority'
6
+
7
+ authority = Localhost::Authority.fetch
8
+ opts = {
9
+ reuse_addr: true,
10
+ dont_linger: true,
11
+ secure_context: authority.server_context
12
+ }
13
+
14
+ server = Polyphony::Net.tcp_listen('localhost', 1234, opts)
15
+
16
+ puts 'Serving HTTPS on port 1234'
17
+
18
+ spin_loop(interval: 1) { STDOUT << '.' }
19
+
20
+ # server.accept_loop do |socket|
21
+ while (socket = server.accept)
22
+ spin do
23
+ while (data = socket.gets("\n", 8192))
24
+ if data.chomp.empty?
25
+ socket << "HTTP/1.1 200 OK\nConnection: close\nContent-Length: 4\n\nfoo\n"
26
+ break
27
+ end
28
+ end
29
+ end
30
+ end
@@ -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;
@@ -135,9 +138,9 @@ inline double current_time() {
135
138
  }
136
139
 
137
140
  inline VALUE backend_timeout_exception(VALUE exception) {
138
- if (RTEST(rb_obj_is_kind_of(exception, rb_cArray)))
141
+ if (rb_obj_is_kind_of(exception, rb_cArray) == Qtrue)
139
142
  return rb_funcall(rb_ary_entry(exception, 0), ID_new, 1, rb_ary_entry(exception, 1));
140
- else if (RTEST(rb_obj_is_kind_of(exception, rb_cClass)))
143
+ else if (rb_obj_is_kind_of(exception, rb_cClass) == Qtrue)
141
144
  return rb_funcall(exception, ID_new, 0);
142
145
  else
143
146
  return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
@@ -154,3 +157,23 @@ VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
154
157
  VALUE Backend_timeout_ensure_safe(VALUE arg) {
155
158
  return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
156
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) {
@@ -406,6 +435,7 @@ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
406
435
  if (underlying_io != Qnil) io = underlying_io;
407
436
  GetOpenFile(io, fptr);
408
437
  rb_io_check_byte_readable(fptr);
438
+ io_unset_nonblock(fptr, io);
409
439
  rectify_io_file_pos(fptr);
410
440
 
411
441
  while (1) {
@@ -445,6 +475,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
445
475
  GetBackend(self, backend);
446
476
  io = rb_io_get_write_io(io);
447
477
  GetOpenFile(io, fptr);
478
+ io_unset_nonblock(fptr, io);
448
479
 
449
480
  char *buf = StringValuePtr(str);
450
481
  long len = RSTRING_LEN(str);
@@ -488,6 +519,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
488
519
  GetBackend(self, backend);
489
520
  io = rb_io_get_write_io(io);
490
521
  GetOpenFile(io, fptr);
522
+ io_unset_nonblock(fptr, io);
491
523
 
492
524
  iov = malloc(iov_count * sizeof(struct iovec));
493
525
  for (int i = 0; i < argc; i++) {
@@ -567,6 +599,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
567
599
  if (underlying_io != Qnil) io = underlying_io;
568
600
  GetOpenFile(io, fptr);
569
601
  rb_io_check_byte_readable(fptr);
602
+ io_unset_nonblock(fptr, io);
570
603
  rectify_io_file_pos(fptr);
571
604
  OBJ_TAINT(str);
572
605
 
@@ -614,6 +647,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
614
647
  if (underlying_io != Qnil) io = underlying_io;
615
648
  GetOpenFile(io, fptr);
616
649
  rb_io_check_byte_readable(fptr);
650
+ io_unset_nonblock(fptr, io);
617
651
  rectify_io_file_pos(fptr);
618
652
 
619
653
  while (1) {
@@ -659,6 +693,7 @@ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method)
659
693
  if (underlying_io != Qnil) io = underlying_io;
660
694
  GetOpenFile(io, fptr);
661
695
  rb_io_check_byte_readable(fptr);
696
+ io_unset_nonblock(fptr, io);
662
697
  rectify_io_file_pos(fptr);
663
698
 
664
699
  while (1) {
@@ -687,7 +722,7 @@ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method)
687
722
  return io;
688
723
  }
689
724
 
690
- VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
725
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
691
726
  Backend_t *backend;
692
727
  rb_io_t *fptr;
693
728
  VALUE underlying_io;
@@ -697,16 +732,18 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
697
732
  GetBackend(self, backend);
698
733
  io = rb_io_get_write_io(io);
699
734
  GetOpenFile(io, fptr);
735
+ io_unset_nonblock(fptr, io);
700
736
 
701
737
  char *buf = StringValuePtr(str);
702
738
  long len = RSTRING_LEN(str);
703
739
  long left = len;
740
+ int flags_int = NUM2INT(flags);
704
741
 
705
742
  while (left > 0) {
706
743
  VALUE resume_value = Qnil;
707
744
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SEND);
708
745
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
709
- io_uring_prep_send(sqe, fptr->fd, buf, left, 0);
746
+ io_uring_prep_send(sqe, fptr->fd, buf, left, flags_int);
710
747
 
711
748
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
712
749
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -734,6 +771,8 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE soc
734
771
  if (underlying_sock != Qnil) server_socket = underlying_sock;
735
772
 
736
773
  GetOpenFile(server_socket, fptr);
774
+ io_unset_nonblock(fptr, server_socket);
775
+
737
776
  while (1) {
738
777
  VALUE resume_value = Qnil;
739
778
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
@@ -787,6 +826,60 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
787
826
  return self;
788
827
  }
789
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
+
790
883
  VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
791
884
  Backend_t *backend;
792
885
  rb_io_t *fptr;
@@ -797,6 +890,7 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
797
890
 
798
891
  GetBackend(self, backend);
799
892
  GetOpenFile(sock, fptr);
893
+ io_unset_nonblock(fptr, sock);
800
894
 
801
895
  addr.sin_family = AF_INET;
802
896
  addr.sin_addr.s_addr = inet_addr(host_buf);
@@ -823,6 +917,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
823
917
  if (underlying_io != Qnil) io = underlying_io;
824
918
  GetBackend(self, backend);
825
919
  GetOpenFile(io, fptr);
920
+ io_unset_nonblock(fptr, io);
826
921
 
827
922
  VALUE resume_value = io_uring_backend_wait_fd(backend, fptr->fd, RTEST(write));
828
923
  RAISE_IF_EXCEPTION(resume_value);
@@ -997,7 +1092,7 @@ VALUE Backend_kind(VALUE self) {
997
1092
  }
998
1093
 
999
1094
  void Init_Backend() {
1000
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1095
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1001
1096
  rb_define_alloc_func(cBackend, Backend_allocate);
1002
1097
 
1003
1098
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -1006,26 +1101,33 @@ void Init_Backend() {
1006
1101
 
1007
1102
  rb_define_method(cBackend, "poll", Backend_poll, 3);
1008
1103
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1104
+ rb_define_method(cBackend, "kind", Backend_kind, 0);
1009
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);
1010
1110
  rb_define_method(cBackend, "read", Backend_read, 4);
1011
1111
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
1012
- rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
1013
- rb_define_method(cBackend, "write", Backend_write_m, -1);
1014
1112
  rb_define_method(cBackend, "recv", Backend_recv, 3);
1015
- rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
1016
1113
  rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
1017
- rb_define_method(cBackend, "send", Backend_send, 2);
1018
- rb_define_method(cBackend, "accept", Backend_accept, 2);
1019
- rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
1020
- rb_define_method(cBackend, "connect", Backend_connect, 3);
1021
- rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1114
+ rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
1115
+ rb_define_method(cBackend, "send", Backend_send, 3);
1116
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
1022
1117
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
1023
- 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);
1024
1120
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
1025
- rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1121
+ rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
1026
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);
1027
1126
 
1028
- 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
1029
1131
 
1030
1132
  SYM_io_uring = ID2SYM(rb_intern("io_uring"));
1031
1133
  }