polyphony 0.49.2 → 0.53.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +34 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +37 -3
  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/io/echo_server.rb +1 -0
  11. data/examples/io/tcp_proxy.rb +2 -2
  12. data/ext/polyphony/backend_common.h +58 -8
  13. data/ext/polyphony/backend_io_uring.c +223 -41
  14. data/ext/polyphony/backend_io_uring_context.c +1 -0
  15. data/ext/polyphony/backend_io_uring_context.h +1 -0
  16. data/ext/polyphony/backend_libev.c +322 -34
  17. data/ext/polyphony/event.c +1 -1
  18. data/ext/polyphony/extconf.rb +9 -2
  19. data/ext/polyphony/fiber.c +2 -1
  20. data/ext/polyphony/polyphony.c +102 -0
  21. data/ext/polyphony/polyphony.h +33 -2
  22. data/ext/polyphony/polyphony_ext.c +3 -0
  23. data/ext/polyphony/queue.c +1 -1
  24. data/ext/polyphony/runqueue.c +7 -1
  25. data/ext/polyphony/runqueue_ring_buffer.c +9 -0
  26. data/ext/polyphony/runqueue_ring_buffer.h +1 -0
  27. data/ext/polyphony/socket_extensions.c +33 -0
  28. data/ext/polyphony/thread.c +14 -0
  29. data/lib/polyphony/adapters/irb.rb +1 -1
  30. data/lib/polyphony/adapters/mysql2.rb +1 -1
  31. data/lib/polyphony/adapters/postgres.rb +5 -5
  32. data/lib/polyphony/adapters/process.rb +4 -4
  33. data/lib/polyphony/core/exceptions.rb +1 -0
  34. data/lib/polyphony/core/global_api.rb +6 -6
  35. data/lib/polyphony/core/sync.rb +1 -1
  36. data/lib/polyphony/core/throttler.rb +1 -1
  37. data/lib/polyphony/core/timer.rb +63 -20
  38. data/lib/polyphony/extensions/core.rb +5 -5
  39. data/lib/polyphony/extensions/fiber.rb +2 -0
  40. data/lib/polyphony/extensions/io.rb +21 -22
  41. data/lib/polyphony/extensions/openssl.rb +6 -6
  42. data/lib/polyphony/extensions/socket.rb +56 -47
  43. data/lib/polyphony/version.rb +1 -1
  44. data/polyphony.gemspec +6 -5
  45. data/test/helper.rb +1 -1
  46. data/test/stress.rb +2 -0
  47. data/test/test_backend.rb +69 -5
  48. data/test/test_fiber.rb +16 -0
  49. data/test/test_global_api.rb +2 -2
  50. data/test/test_io.rb +84 -1
  51. data/test/test_kernel.rb +1 -1
  52. data/test/test_signal.rb +1 -1
  53. data/test/test_socket.rb +61 -0
  54. data/test/test_timer.rb +41 -8
  55. metadata +22 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18278f9c013191fd5a96520a07e3b6e5c4bc088665a1b1a769b02552bd0e5448
4
- data.tar.gz: 4e1e42fa5e2a6ddc6dcff7982415dd16246a0d75212aa2eb9ae7a97e030a2bdb
3
+ metadata.gz: '0682736e3fdca0ada986f4210f1bb25d682c75a7b9d297451c186b90c8025e13'
4
+ data.tar.gz: 5ff46620651c056983a6f73c879ca1c49acea22c0c7abcefb1a119cbe248cb77
5
5
  SHA512:
6
- metadata.gz: 483158709045a9dc59fa75c912067c4fafbb02885e15f3fa57f482b97903a0c919abbb5ad19bea85837222a44d5ec2778f1e1421839c2e6a595a64e84f2d707d
7
- data.tar.gz: c1162de70de5aa2d92804e7c4ccb3a372c5839d5600a7f68ae6f84363677a52f23923dbe8e407a84607ac4f0145736c7a5b9ada8b4956fd9c742cec182ed3a70
6
+ metadata.gz: cfbb8141b21cc9e56cdcd9a34fd55cd3b48573dcc3011f127ece5049b13ca2c7fa8a17c8525ba5996ab97afcbfc8e46064a3d68c79d71d44d40c274ff9051d2d
7
+ data.tar.gz: f31d711c88fe354b8ffd63e37f30887b1bfb5c3709d3760412ee4ec4ea06d14c0a17e668ddd750958295dc12307b96cf64874b1be4ed46b8564d04236114ee67
@@ -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,37 @@
1
+ ## 0.53.0
2
+
3
+ - Implement `Backend#splice`, `Backend#splice_to_eof`, along with `IO#splice`, `IO#splice_to_eof`
4
+
5
+ ## 0.52.0
6
+
7
+ - Polyphony is now compatible with Ruby 3.0
8
+ - Add `Backend#sendv` method for sending multiple strings
9
+ - Accept flags argument in `Backend#send` (#48)
10
+ - Fix io_uring backend on Ruby 3.0 (#47)
11
+ - Implement C-based public backend API: `Polyphony.backend_XXXX` methods
12
+ - libev backend: Use` pidfd_open` for Linux 5.3+, otherwise use a libev child watcher
13
+ - Use `:call` as default method in `#feed_loop`
14
+
15
+ ## 0.51.0
16
+
17
+ - Implement `IO#feed_loop`, `Socket#feed_loop`
18
+ - Fix error handling in `Process.kill_and_await`
19
+
20
+ ## 0.50.1
21
+
22
+ - Set `IOSQE_ASYNC` flag in io_uring backend
23
+ - Fix error handling in `Backend#waitpid`
24
+ - Reimplement libev backend's `#waitpid` by using pidfd_open (in similar manner
25
+ to the io_uring backend)
26
+
27
+ ## 0.50.0
28
+
29
+ - Use `Process::CLOCK_MONOTONIC` in Timer
30
+ - Add `Timer#sleep`, `Timer#after`, `Timer#every`
31
+ - Prevent fiber from being resumed after terminating
32
+ - Add `Thread#fiber_index_of` method
33
+ - Use `Backend#wait_event` in `Fiber#await`
34
+
1
35
  ## 0.49.2
2
36
 
3
37
  - Fix hang with 100s or more child fibers when terminating
data/Gemfile.lock CHANGED
@@ -1,101 +1,50 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.49.2)
4
+ polyphony (0.53.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,8 @@
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
 
@@ -13,17 +18,46 @@
13
18
  - `Fiber#receive_loop` (very little effort, should be implemented in C)
14
19
 
15
20
 
16
- - Add `Backend#splice`, `Backend#splice_loop` for implementing stuff like proxying:
21
+ - Add `Backend#splice`, `Backend#splice_to_eof` for implementing stuff like proxying:
17
22
 
18
23
  ```ruby
19
24
  def two_way_proxy(socket1, socket2)
20
25
  backend = Thread.current.backend
21
- f1 = spin { backend.splice_loop(socket1, socket2) }
22
- 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) }
23
28
  Fiber.await(f1, f2)
24
29
  end
25
30
  ```
26
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
+ [sock, :<<, chunk_header(len)],
39
+ [sock, :splice, file, 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
+ [sock, :<<, chunk_header(len)],
55
+ [sock, :splice, file, len]
56
+ )
57
+ end
58
+ end
59
+ ```
60
+
27
61
  - Graceful shutdown again:
28
62
  - What happens to children when doing a graceful shutdown?
29
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,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
@@ -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
@@ -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
+ }