polyphony 0.49.1 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +35 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +6 -0
  6. data/examples/core/forking.rb +2 -2
  7. data/examples/core/nested.rb +21 -0
  8. data/examples/core/suspend.rb +13 -0
  9. data/examples/core/terminate_main_fiber.rb +12 -0
  10. data/examples/performance/thread-vs-fiber/polyphony_server.rb +2 -4
  11. data/ext/polyphony/backend_common.h +58 -8
  12. data/ext/polyphony/backend_io_uring.c +158 -35
  13. data/ext/polyphony/backend_libev.c +192 -25
  14. data/ext/polyphony/event.c +1 -1
  15. data/ext/polyphony/extconf.rb +7 -2
  16. data/ext/polyphony/fiber.c +2 -1
  17. data/ext/polyphony/polyphony.c +94 -0
  18. data/ext/polyphony/polyphony.h +29 -2
  19. data/ext/polyphony/queue.c +1 -1
  20. data/ext/polyphony/runqueue.c +7 -1
  21. data/ext/polyphony/runqueue_ring_buffer.c +9 -0
  22. data/ext/polyphony/runqueue_ring_buffer.h +1 -0
  23. data/ext/polyphony/thread.c +14 -0
  24. data/lib/polyphony/adapters/irb.rb +1 -1
  25. data/lib/polyphony/adapters/mysql2.rb +1 -1
  26. data/lib/polyphony/adapters/postgres.rb +5 -5
  27. data/lib/polyphony/adapters/process.rb +4 -4
  28. data/lib/polyphony/core/exceptions.rb +1 -0
  29. data/lib/polyphony/core/global_api.rb +6 -6
  30. data/lib/polyphony/core/sync.rb +1 -1
  31. data/lib/polyphony/core/throttler.rb +1 -1
  32. data/lib/polyphony/core/timer.rb +63 -20
  33. data/lib/polyphony/extensions/core.rb +5 -5
  34. data/lib/polyphony/extensions/fiber.rb +11 -8
  35. data/lib/polyphony/extensions/io.rb +13 -22
  36. data/lib/polyphony/extensions/openssl.rb +6 -6
  37. data/lib/polyphony/extensions/socket.rb +41 -41
  38. data/lib/polyphony/extensions/thread.rb +1 -2
  39. data/lib/polyphony/version.rb +1 -1
  40. data/polyphony.gemspec +6 -5
  41. data/test/helper.rb +2 -3
  42. data/test/stress.rb +2 -0
  43. data/test/test_backend.rb +58 -5
  44. data/test/test_fiber.rb +31 -0
  45. data/test/test_global_api.rb +2 -2
  46. data/test/test_io.rb +84 -1
  47. data/test/test_kernel.rb +1 -1
  48. data/test/test_signal.rb +2 -3
  49. data/test/test_socket.rb +61 -0
  50. data/test/test_timer.rb +41 -8
  51. metadata +21 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d455bffada65a186f47a33dec9a29bf81a8a025b4c536a0d3136e256a3aaa55
4
- data.tar.gz: '094f9bae247c640ac2ea721625b5dd4b58ae4943186bfc015c9c830117a9dbee'
3
+ metadata.gz: 2056b02e3b364911ab627ffc49a7251ad2eed68ba6b5b61d380c599127c181f6
4
+ data.tar.gz: 440f57d240b90a255b470f405980b5fa2f806ba3ff2640f31d78b6403de7ce0e
5
5
  SHA512:
6
- metadata.gz: e8a462663b9e1bca1f99b301c8d904b5fad091851dff9cf5af785bf3a76802ddeb5904565b4a79e916b8a4b97001181a48bed7a56dadf1143dfc11d9ae7762d2
7
- data.tar.gz: 68765d941201a2d74ae1e8cc20a9ea3c6fba9e68b6a552dc65a399a7ad8853a619a259ebc1dc705cdb2f3eae77adee4098de1186212755f7d87064bbaaf125f8
6
+ metadata.gz: b5bf34a4ea7addf2b71c2e0acc75041d95f5d76ae425061bd5475e6523111a05cf969b0109ec90874f7f1a1929111182ed193b084b3388be060886145dd20567
7
+ data.tar.gz: 6165128a0393664c97baa494119e453db177b7d0a12c5a5ade0ab5bb8e77a4143f6156b9cb594ef2edbbe9e0ac3bb85a2c846da8e925c3a6ec1c294120c18bf2
@@ -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,38 @@
1
+ ## 0.52.0
2
+
3
+ - Polyphony is now compatible with Ruby 3.0
4
+ - Add `Backend#sendv` method for sending multiple strings
5
+ - Accept flags argument in `Backend#send` (#48)
6
+ - Fix io_uring backend on Ruby 3.0 (#47)
7
+ - Implement C-based public backend API: `Polyphony.backend_XXXX` methods
8
+ - libev backend: Use` pidfd_open` for Linux 5.3+, otherwise use a libev child watcher
9
+ - Use `:call` as default method in `#feed_loop`
10
+
11
+ ## 0.51.0
12
+
13
+ - Implement `IO#feed_loop`, `Socket#feed_loop`
14
+ - Fix error handling in `Process.kill_and_await`
15
+
16
+ ## 0.50.1
17
+
18
+ - Set `IOSQE_ASYNC` flag in io_uring backend
19
+ - Fix error handling in `Backend#waitpid`
20
+ - Reimplement libev backend's `#waitpid` by using pidfd_open (in similar manner
21
+ to the io_uring backend)
22
+
23
+ ## 0.50.0
24
+
25
+ - Use `Process::CLOCK_MONOTONIC` in Timer
26
+ - Add `Timer#sleep`, `Timer#after`, `Timer#every`
27
+ - Prevent fiber from being resumed after terminating
28
+ - Add `Thread#fiber_index_of` method
29
+ - Use `Backend#wait_event` in `Fiber#await`
30
+
31
+ ## 0.49.2
32
+
33
+ - Fix hang with 100s or more child fibers when terminating
34
+ - Fix double pending_count increment in io_uring backend
35
+
1
36
  ## 0.49.1
2
37
 
3
38
  - Use `TCPSocket` instead of `Socket` in `Net.tcp_connect`
data/Gemfile.lock CHANGED
@@ -1,101 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.49.1)
4
+ polyphony (0.52.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)
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,3 +1,9 @@
1
+ - Implement io_uring Backend_send with variable arity.
2
+ - Implement a buffer store for use in:
3
+ - io_uring Backend_send_m
4
+ - io_uring Backend_writev (for iov)
5
+ - libvev Backend_writev (for iov)
6
+
1
7
  - Check segfault when resetting a `cancel_after` timeout lots of times at very high rate
2
8
  - Check why `throttled_loop` inside of `move_on_after` fails to stop
3
9
 
@@ -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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ def process
7
+ p :b_start
8
+ sleep 1
9
+ p :b_stop
10
+ end
11
+
12
+ spin do
13
+ p :a_start
14
+ spin { process }
15
+ sleep 60
16
+ p :a_stop
17
+ end
18
+
19
+ p :main_start
20
+ sleep 120
21
+ p :main_stop
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ main = Fiber.current
7
+ spin do
8
+ sleep 0.1
9
+ main.schedule(:foo)
10
+ end
11
+
12
+ v = suspend
13
+ puts "v => #{v.inspect}"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ main = Fiber.current
7
+ spin do
8
+ sleep 0.1
9
+ main.terminate
10
+ end
11
+
12
+ sleep
@@ -26,15 +26,13 @@ def write_response(socket)
26
26
  socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
27
27
  end
28
28
 
29
- server = TCPServer.open('0.0.0.0', 1234)
30
- puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 1234"
29
+ server = TCPServer.open('0.0.0.0', 4411)
30
+ puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 4411"
31
31
 
32
32
  spin_loop(interval: 10) do
33
33
  p Thread.current.fiber_scheduling_stats
34
34
  end
35
35
 
36
- GC.disable
37
-
38
36
  server.accept_loop do |c|
39
37
  spin { handle_client(c) }
40
38
  end
@@ -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
@@ -114,16 +132,48 @@ inline double current_time() {
114
132
  struct timespec ts;
115
133
  clock_gettime(CLOCK_MONOTONIC, &ts);
116
134
  long long ns = ts.tv_sec;
117
- ns = ns * 1000000000 + ts.tv_nsec;
135
+ ns = ns * 1e9 + ts.tv_nsec;
118
136
  double t = ns;
119
137
  return t / 1e9;
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,35 @@
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
+ #define io_unset_nonblock(fptr, io)
47
+ #endif
31
48
 
32
49
  typedef struct Backend_t {
33
50
  // common fields
@@ -260,12 +277,10 @@ int io_uring_backend_defer_submit_and_await(
260
277
  VALUE switchpoint_result = Qnil;
261
278
 
262
279
  io_uring_sqe_set_data(sqe, ctx);
263
- // io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
280
+ io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
264
281
  io_uring_backend_defer_submit(backend);
265
282
 
266
- backend->pending_count++;
267
283
  switchpoint_result = backend_await(backend);
268
- backend->pending_count--;
269
284
 
270
285
  if (!ctx->completed) {
271
286
  ctx->result = -ECANCELED;
@@ -310,6 +325,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
310
325
  if (underlying_io != Qnil) io = underlying_io;
311
326
  GetOpenFile(io, fptr);
312
327
  rb_io_check_byte_readable(fptr);
328
+ io_unset_nonblock(fptr, io);
313
329
  rectify_io_file_pos(fptr);
314
330
  OBJ_TAINT(str);
315
331
 
@@ -370,6 +386,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
370
386
  if (underlying_io != Qnil) io = underlying_io;
371
387
  GetOpenFile(io, fptr);
372
388
  rb_io_check_byte_readable(fptr);
389
+ io_unset_nonblock(fptr, io);
373
390
  rectify_io_file_pos(fptr);
374
391
 
375
392
  while (1) {
@@ -399,6 +416,53 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
399
416
  return io;
400
417
  }
401
418
 
419
+ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
420
+ Backend_t *backend;
421
+ rb_io_t *fptr;
422
+ VALUE str;
423
+ long total;
424
+ long len = 8192;
425
+ int shrinkable;
426
+ char *buf;
427
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
428
+ ID method_id = SYM2ID(method);
429
+
430
+ READ_LOOP_PREPARE_STR();
431
+
432
+ GetBackend(self, backend);
433
+ if (underlying_io != Qnil) io = underlying_io;
434
+ GetOpenFile(io, fptr);
435
+ rb_io_check_byte_readable(fptr);
436
+ io_unset_nonblock(fptr, io);
437
+ rectify_io_file_pos(fptr);
438
+
439
+ while (1) {
440
+ VALUE resume_value = Qnil;
441
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_READ);
442
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
443
+ io_uring_prep_read(sqe, fptr->fd, buf, len, -1);
444
+
445
+ ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
446
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
447
+ RAISE_IF_EXCEPTION(resume_value);
448
+ if (!ctx->completed) return resume_value;
449
+ RB_GC_GUARD(resume_value);
450
+
451
+ if (result < 0)
452
+ rb_syserr_fail(-result, strerror(-result));
453
+ else if (!result)
454
+ break; // EOF
455
+ else {
456
+ total = result;
457
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
458
+ }
459
+ }
460
+
461
+ RB_GC_GUARD(str);
462
+
463
+ return io;
464
+ }
465
+
402
466
  VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
403
467
  Backend_t *backend;
404
468
  rb_io_t *fptr;
@@ -409,6 +473,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
409
473
  GetBackend(self, backend);
410
474
  io = rb_io_get_write_io(io);
411
475
  GetOpenFile(io, fptr);
476
+ io_unset_nonblock(fptr, io);
412
477
 
413
478
  char *buf = StringValuePtr(str);
414
479
  long len = RSTRING_LEN(str);
@@ -452,6 +517,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
452
517
  GetBackend(self, backend);
453
518
  io = rb_io_get_write_io(io);
454
519
  GetOpenFile(io, fptr);
520
+ io_unset_nonblock(fptr, io);
455
521
 
456
522
  iov = malloc(iov_count * sizeof(struct iovec));
457
523
  for (int i = 0; i < argc; i++) {
@@ -531,6 +597,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
531
597
  if (underlying_io != Qnil) io = underlying_io;
532
598
  GetOpenFile(io, fptr);
533
599
  rb_io_check_byte_readable(fptr);
600
+ io_unset_nonblock(fptr, io);
534
601
  rectify_io_file_pos(fptr);
535
602
  OBJ_TAINT(str);
536
603
 
@@ -578,6 +645,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
578
645
  if (underlying_io != Qnil) io = underlying_io;
579
646
  GetOpenFile(io, fptr);
580
647
  rb_io_check_byte_readable(fptr);
648
+ io_unset_nonblock(fptr, io);
581
649
  rectify_io_file_pos(fptr);
582
650
 
583
651
  while (1) {
@@ -606,7 +674,53 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
606
674
  return io;
607
675
  }
608
676
 
609
- VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
677
+ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
678
+ Backend_t *backend;
679
+ rb_io_t *fptr;
680
+ VALUE str;
681
+ long total;
682
+ long len = 8192;
683
+ int shrinkable;
684
+ char *buf;
685
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
686
+ ID method_id = SYM2ID(method);
687
+
688
+ READ_LOOP_PREPARE_STR();
689
+
690
+ GetBackend(self, backend);
691
+ if (underlying_io != Qnil) io = underlying_io;
692
+ GetOpenFile(io, fptr);
693
+ rb_io_check_byte_readable(fptr);
694
+ io_unset_nonblock(fptr, io);
695
+ rectify_io_file_pos(fptr);
696
+
697
+ while (1) {
698
+ VALUE resume_value = Qnil;
699
+ op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_RECV);
700
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
701
+ io_uring_prep_recv(sqe, fptr->fd, buf, len, 0);
702
+
703
+ int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
704
+ OP_CONTEXT_RELEASE(&backend->store, ctx);
705
+ RAISE_IF_EXCEPTION(resume_value);
706
+ if (!ctx->completed) return resume_value;
707
+ RB_GC_GUARD(resume_value);
708
+
709
+ if (result < 0)
710
+ rb_syserr_fail(-result, strerror(-result));
711
+ else if (!result)
712
+ break; // EOF
713
+ else {
714
+ total = result;
715
+ READ_LOOP_PASS_STR_TO_RECEIVER(receiver, method_id);
716
+ }
717
+ }
718
+
719
+ RB_GC_GUARD(str);
720
+ return io;
721
+ }
722
+
723
+ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
610
724
  Backend_t *backend;
611
725
  rb_io_t *fptr;
612
726
  VALUE underlying_io;
@@ -616,16 +730,18 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
616
730
  GetBackend(self, backend);
617
731
  io = rb_io_get_write_io(io);
618
732
  GetOpenFile(io, fptr);
733
+ io_unset_nonblock(fptr, io);
619
734
 
620
735
  char *buf = StringValuePtr(str);
621
736
  long len = RSTRING_LEN(str);
622
737
  long left = len;
738
+ int flags_int = NUM2INT(flags);
623
739
 
624
740
  while (left > 0) {
625
741
  VALUE resume_value = Qnil;
626
742
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_SEND);
627
743
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
628
- io_uring_prep_send(sqe, fptr->fd, buf, left, 0);
744
+ io_uring_prep_send(sqe, fptr->fd, buf, left, flags_int);
629
745
 
630
746
  int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
631
747
  OP_CONTEXT_RELEASE(&backend->store, ctx);
@@ -653,6 +769,8 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE soc
653
769
  if (underlying_sock != Qnil) server_socket = underlying_sock;
654
770
 
655
771
  GetOpenFile(server_socket, fptr);
772
+ io_unset_nonblock(fptr, server_socket);
773
+
656
774
  while (1) {
657
775
  VALUE resume_value = Qnil;
658
776
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
@@ -716,6 +834,7 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
716
834
 
717
835
  GetBackend(self, backend);
718
836
  GetOpenFile(sock, fptr);
837
+ io_unset_nonblock(fptr, sock);
719
838
 
720
839
  addr.sin_family = AF_INET;
721
840
  addr.sin_addr.s_addr = inet_addr(host_buf);
@@ -742,6 +861,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
742
861
  if (underlying_io != Qnil) io = underlying_io;
743
862
  GetBackend(self, backend);
744
863
  GetOpenFile(io, fptr);
864
+ io_unset_nonblock(fptr, io);
745
865
 
746
866
  VALUE resume_value = io_uring_backend_wait_fd(backend, fptr->fd, RTEST(write));
747
867
  RAISE_IF_EXCEPTION(resume_value);
@@ -813,18 +933,6 @@ VALUE Backend_timer_loop(VALUE self, VALUE interval) {
813
933
  }
814
934
  }
815
935
 
816
- VALUE Backend_timeout_safe(VALUE arg) {
817
- return rb_yield(arg);
818
- }
819
-
820
- VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
821
- return exception;
822
- }
823
-
824
- VALUE Backend_timeout_ensure_safe(VALUE arg) {
825
- return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
826
- }
827
-
828
936
  struct Backend_timeout_ctx {
829
937
  Backend_t *backend;
830
938
  op_context_t *ctx;
@@ -863,7 +971,6 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
863
971
  ctx->resume_value = timeout;
864
972
  io_uring_prep_timeout(sqe, &ts, 0, 0);
865
973
  io_uring_sqe_set_data(sqe, ctx);
866
- io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
867
974
  io_uring_backend_defer_submit(backend);
868
975
 
869
976
  struct Backend_timeout_ctx timeout_ctx = {backend, ctx};
@@ -881,19 +988,28 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
881
988
  }
882
989
 
883
990
  VALUE Backend_waitpid(VALUE self, VALUE pid) {
884
- Backend_t *backend;
885
991
  int pid_int = NUM2INT(pid);
886
992
  int fd = pidfd_open(pid_int, 0);
887
- GetBackend(self, backend);
888
-
889
- VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
890
- close(fd);
891
993
 
892
- RAISE_IF_EXCEPTION(resume_value);
893
- RB_GC_GUARD(resume_value);
994
+ if (fd >= 0) {
995
+ Backend_t *backend;
996
+ GetBackend(self, backend);
894
997
 
998
+ VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
999
+ close(fd);
1000
+ RAISE_IF_EXCEPTION(resume_value);
1001
+ RB_GC_GUARD(resume_value);
1002
+ }
1003
+
895
1004
  int status;
896
1005
  pid_t ret = waitpid(pid_int, &status, WNOHANG);
1006
+ if (ret < 0) {
1007
+ int e = errno;
1008
+ if (e == ECHILD)
1009
+ ret = pid_int;
1010
+ else
1011
+ rb_syserr_fail(e, strerror(e));
1012
+ }
897
1013
  return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
898
1014
  }
899
1015
 
@@ -920,7 +1036,7 @@ VALUE Backend_kind(VALUE self) {
920
1036
  }
921
1037
 
922
1038
  void Init_Backend() {
923
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
1039
+ VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
924
1040
  rb_define_alloc_func(cBackend, Backend_allocate);
925
1041
 
926
1042
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -932,10 +1048,13 @@ void Init_Backend() {
932
1048
 
933
1049
  rb_define_method(cBackend, "read", Backend_read, 4);
934
1050
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
1051
+ rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
935
1052
  rb_define_method(cBackend, "write", Backend_write_m, -1);
936
1053
  rb_define_method(cBackend, "recv", Backend_recv, 3);
937
1054
  rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
938
- rb_define_method(cBackend, "send", Backend_send, 2);
1055
+ rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
1056
+ rb_define_method(cBackend, "send", Backend_send, 3);
1057
+ rb_define_method(cBackend, "sendv", Backend_sendv, 3);
939
1058
  rb_define_method(cBackend, "accept", Backend_accept, 2);
940
1059
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
941
1060
  rb_define_method(cBackend, "connect", Backend_connect, 3);
@@ -948,6 +1067,10 @@ void Init_Backend() {
948
1067
 
949
1068
  rb_define_method(cBackend, "kind", Backend_kind, 0);
950
1069
 
1070
+ #ifdef POLYPHONY_UNSET_NONBLOCK
1071
+ ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
1072
+ #endif
1073
+
951
1074
  SYM_io_uring = ID2SYM(rb_intern("io_uring"));
952
1075
  }
953
1076