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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.49.2'
4
+ VERSION = '0.53.0'
5
5
  end
data/polyphony.gemspec CHANGED
@@ -22,12 +22,13 @@ Gem::Specification.new do |s|
22
22
  s.required_ruby_version = '>= 2.6'
23
23
 
24
24
  s.add_development_dependency 'rake-compiler', '1.1.1'
25
- s.add_development_dependency 'minitest', '5.13.0'
25
+ s.add_development_dependency 'minitest', '5.14.4'
26
26
  s.add_development_dependency 'minitest-reporters', '1.4.2'
27
27
  s.add_development_dependency 'simplecov', '0.17.1'
28
28
  s.add_development_dependency 'rubocop', '0.85.1'
29
29
  s.add_development_dependency 'pry', '0.13.1'
30
30
 
31
+ s.add_development_dependency 'msgpack', '1.4.2'
31
32
  s.add_development_dependency 'pg', '1.1.4'
32
33
  s.add_development_dependency 'redis', '4.1.0'
33
34
  s.add_development_dependency 'hiredis', '0.6.3'
@@ -37,8 +38,8 @@ Gem::Specification.new do |s|
37
38
  s.add_development_dependency 'sequel', '5.34.0'
38
39
  s.add_development_dependency 'httparty', '0.17.1'
39
40
 
40
- s.add_development_dependency 'jekyll', '~>3.8.6'
41
- s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
42
- s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
43
- s.add_development_dependency 'just-the-docs', '~>0.3.0'
41
+ # s.add_development_dependency 'jekyll', '~>3.8.6'
42
+ # s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
43
+ # s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
44
+ # s.add_development_dependency 'just-the-docs', '~>0.3.0'
44
45
  end
data/test/helper.rb CHANGED
@@ -52,7 +52,7 @@ class MiniTest::Test
52
52
  Fiber.current.instance_variable_set(:@auto_watcher, nil)
53
53
  Thread.current.backend.finalize
54
54
  Thread.current.backend = Polyphony::Backend.new
55
- sleep 0
55
+ sleep 0.001
56
56
  end
57
57
 
58
58
  def teardown
data/test/stress.rb CHANGED
@@ -8,6 +8,8 @@ def run_test(count)
8
8
  puts "#{count}: running tests..."
9
9
  # sleep 1
10
10
  system(TEST_CMD)
11
+ puts
12
+
11
13
  return if $?.exitstatus == 0
12
14
 
13
15
  puts "Failure after #{count} tests"
data/test/test_backend.rb CHANGED
@@ -125,12 +125,15 @@ class BackendTest < MiniTest::Test
125
125
  assert_equal [:ready, 'foo', 'bar'], buf
126
126
  end
127
127
 
128
- def test_accept_loop
129
- server = TCPServer.new('127.0.0.1', 1234)
128
+ Net = Polyphony::Net
129
+
130
+ def test_accept
131
+ server = Net.listening_socket_from_options('127.0.0.1', 1234, reuse_addr: true)
130
132
 
131
133
  clients = []
132
- server_fiber = spin do
133
- @backend.accept_loop(server, TCPSocket) { |c| clients << c }
134
+ server_fiber = spin_loop do
135
+ c = @backend.accept(server, TCPSocket)
136
+ clients << c
134
137
  end
135
138
 
136
139
  c1 = TCPSocket.new('127.0.0.1', 1234)
@@ -146,7 +149,32 @@ class BackendTest < MiniTest::Test
146
149
  ensure
147
150
  c1&.close
148
151
  c2&.close
149
- server_fiber.stop
152
+ server_fiber&.stop
153
+ snooze
154
+ server&.close
155
+ end
156
+
157
+ def test_accept_loop
158
+ server = Net.listening_socket_from_options('127.0.0.1', 1235, reuse_addr: true)
159
+
160
+ clients = []
161
+ server_fiber = spin do
162
+ @backend.accept_loop(server, TCPSocket) { |c| clients << c }
163
+ end
164
+
165
+ c1 = TCPSocket.new('127.0.0.1', 1235)
166
+ sleep 0.01
167
+
168
+ assert_equal 1, clients.size
169
+
170
+ c2 = TCPSocket.new('127.0.0.1', 1235)
171
+ sleep 0.01
172
+
173
+ assert_equal 2, clients.size
174
+ ensure
175
+ c1&.close
176
+ c2&.close
177
+ server_fiber&.stop
150
178
  snooze
151
179
  server&.close
152
180
  end
@@ -209,4 +237,40 @@ class BackendTest < MiniTest::Test
209
237
  end
210
238
  assert_equal [1], buffer
211
239
  end
240
+
241
+ def test_splice
242
+ i1, o1 = IO.pipe
243
+ i2, o2 = IO.pipe
244
+
245
+ spin {
246
+ o2.splice(i1, 1000)
247
+ o2.close
248
+ }
249
+
250
+ o1.write('foobar')
251
+ result = i2.read
252
+
253
+ assert_equal 'foobar', result
254
+ end
255
+
256
+ def test_splice_to_eof
257
+ i1, o1 = IO.pipe
258
+ i2, o2 = IO.pipe
259
+
260
+ f = spin {
261
+ o2.splice_to_eof(i1, 1000)
262
+ o2.close
263
+ }
264
+
265
+ o1.write('foo')
266
+ result = i2.readpartial(1000)
267
+ assert_equal 'foo', result
268
+
269
+ o1.write('bar')
270
+ result = i2.readpartial(1000)
271
+ assert_equal 'bar', result
272
+ ensure
273
+ f.interrupt
274
+ f.await
275
+ end
212
276
  end
data/test/test_fiber.rb CHANGED
@@ -349,6 +349,22 @@ class FiberTest < MiniTest::Test
349
349
  assert_equal [:foo, :terminate], buffer
350
350
  end
351
351
 
352
+ CMD_TERMINATE_MAIN_FIBER = <<~BASH
353
+ ruby -rbundler/setup -rpolyphony -e"spin { sleep 0.1; Thread.current.main_fiber.terminate }; begin; sleep; rescue Polyphony::Terminate; STDOUT << 'terminated'; end" 2>&1
354
+ BASH
355
+
356
+ CMD_TERMINATE_CHILD_FIBER = <<~BASH
357
+ ruby -rbundler/setup -rpolyphony -e"f = spin { sleep }; spin { sleep 0.1; f.terminate }; f.await" 2>&1
358
+ BASH
359
+
360
+ def test_terminate_main_fiber
361
+ output = `#{CMD_TERMINATE_CHILD_FIBER}`
362
+ assert_equal '', output
363
+
364
+ output = `#{CMD_TERMINATE_MAIN_FIBER}`
365
+ assert_equal 'terminated', output
366
+ end
367
+
352
368
  def test_interrupt_timer
353
369
  result = []
354
370
  f = Fiber.current.spin do
@@ -160,10 +160,10 @@ class MoveOnAfterTest < MiniTest::Test
160
160
  end
161
161
  t1 = Time.now
162
162
  assert_equal 1, o
163
- assert_in_range 0.008..0.013, t1 - t0
163
+ assert_in_range 0.008..0.015, t1 - t0
164
164
 
165
165
  t0 = Time.now
166
- o = move_on_after(0.02, with_value: 1) do
166
+ o = move_on_after(0.05, with_value: 1) do
167
167
  move_on_after(0.01, with_value: 2) do
168
168
  sleep 1
169
169
  end
data/test/test_io.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'helper'
4
+ require 'msgpack'
4
5
 
5
6
  class IOTest < MiniTest::Test
6
7
  def setup
@@ -110,7 +111,7 @@ class IOTest < MiniTest::Test
110
111
  assert_equal [], buf
111
112
 
112
113
  o << "ulous\n"
113
- 10.times { snooze }
114
+ sleep 0.01
114
115
  assert_equal ["fabulous\n"], buf
115
116
 
116
117
  o.close
@@ -173,6 +174,88 @@ class IOTest < MiniTest::Test
173
174
 
174
175
  assert_equal 'hello: world', buf
175
176
  end
177
+
178
+ def test_feed_loop_with_block
179
+ i, o = IO.pipe
180
+ unpacker = MessagePack::Unpacker.new
181
+ buffer = []
182
+ reader = spin do
183
+ i.feed_loop(unpacker, :feed_each) { |msg| buffer << msg }
184
+ end
185
+ o << 'foo'.to_msgpack
186
+ sleep 0.01
187
+ assert_equal ['foo'], buffer
188
+
189
+ o << 'bar'.to_msgpack
190
+ sleep 0.01
191
+ assert_equal ['foo', 'bar'], buffer
192
+
193
+ o << 'baz'.to_msgpack
194
+ sleep 0.01
195
+ assert_equal ['foo', 'bar', 'baz'], buffer
196
+ end
197
+
198
+ class Receiver1
199
+ attr_reader :buffer
200
+
201
+ def initialize
202
+ @buffer = []
203
+ end
204
+
205
+ def recv(obj)
206
+ @buffer << obj
207
+ end
208
+ end
209
+
210
+ def test_feed_loop_without_block
211
+ i, o = IO.pipe
212
+ receiver = Receiver1.new
213
+ reader = spin do
214
+ i.feed_loop(receiver, :recv)
215
+ end
216
+ o << 'foo'
217
+ sleep 0.01
218
+ assert_equal ['foo'], receiver.buffer
219
+
220
+ o << 'bar'
221
+ sleep 0.01
222
+ assert_equal ['foo', 'bar'], receiver.buffer
223
+
224
+ o << 'baz'
225
+ sleep 0.01
226
+ assert_equal ['foo', 'bar', 'baz'], receiver.buffer
227
+ end
228
+
229
+ class Receiver2
230
+ attr_reader :buffer
231
+
232
+ def initialize
233
+ @buffer = []
234
+ end
235
+
236
+ def call(obj)
237
+ @buffer << obj
238
+ end
239
+ end
240
+
241
+ def test_feed_loop_without_method
242
+ i, o = IO.pipe
243
+ receiver = Receiver2.new
244
+ reader = spin do
245
+ i.feed_loop(receiver)
246
+ end
247
+ o << 'foo'
248
+ sleep 0.01
249
+ assert_equal ['foo'], receiver.buffer
250
+
251
+ o << 'bar'
252
+ sleep 0.01
253
+ assert_equal ['foo', 'bar'], receiver.buffer
254
+
255
+ o << 'baz'
256
+ sleep 0.01
257
+ assert_equal ['foo', 'bar', 'baz'], receiver.buffer
258
+ end
176
259
  end
177
260
 
178
261
  class IOClassMethodsTest < MiniTest::Test
data/test/test_kernel.rb CHANGED
@@ -21,7 +21,7 @@ class KernelTest < MiniTest::Test
21
21
 
22
22
  def test_Kernel_system_singleton_method
23
23
  assert_equal true, Kernel.system("which ruby > /dev/null 2>&1")
24
- assert_equal false, Kernel.system("which rruubbyy > /dev/null 2>&1")
24
+ assert_equal false, Kernel.system("azertyuiop > /dev/null 2>&1")
25
25
  end
26
26
 
27
27
  def patch_open3
data/test/test_signal.rb CHANGED
@@ -23,7 +23,7 @@ class SignalTrapTest < Minitest::Test
23
23
  ensure
24
24
  o.close
25
25
  end
26
- sleep 0.01
26
+ sleep 0.1
27
27
  o.close
28
28
  Process.kill('INT', pid)
29
29
  Thread.current.backend.waitpid(pid)
data/test/test_socket.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'helper'
4
4
  require 'fileutils'
5
+ require 'msgpack'
5
6
 
6
7
  class SocketTest < MiniTest::Test
7
8
  def setup
@@ -33,6 +34,66 @@ class SocketTest < MiniTest::Test
33
34
  server&.close
34
35
  end
35
36
 
37
+ # sending multiple strings at once
38
+ def test_sendv
39
+ port = rand(1234..5678)
40
+ server = TCPServer.new('127.0.0.1', port)
41
+
42
+ server_fiber = spin do
43
+ while (socket = server.accept)
44
+ spin do
45
+ while (data = socket.gets(8192))
46
+ socket.write("you said ", data)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ snooze
53
+ client = TCPSocket.new('127.0.0.1', port)
54
+ client.write("1234\n")
55
+ assert_equal "you said 1234\n", client.recv(8192)
56
+ client.close
57
+ ensure
58
+ server_fiber&.stop
59
+ server_fiber&.await
60
+ server&.close
61
+ end
62
+
63
+
64
+ def test_feed_loop
65
+ port = rand(1234..5678)
66
+ server = TCPServer.new('127.0.0.1', port)
67
+
68
+ server_fiber = spin do
69
+ reader = MessagePack::Unpacker.new
70
+ while (socket = server.accept)
71
+ spin do
72
+ socket.feed_loop(reader, :feed_each) do |msg|
73
+ msg = { 'result' => msg['x'] + msg['y'] }
74
+ socket << msg.to_msgpack
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ snooze
81
+ client = TCPSocket.new('127.0.0.1', port)
82
+ reader = MessagePack::Unpacker.new
83
+ client << { 'x' => 13, 'y' => 14 }.to_msgpack
84
+ result = nil
85
+ client.feed_loop(reader, :feed_each) do |msg|
86
+ result = msg
87
+ break
88
+ end
89
+ assert_equal({ 'result' => 27}, result)
90
+ client.close
91
+ ensure
92
+ server_fiber&.stop
93
+ server_fiber&.await
94
+ server&.close
95
+ end
96
+
36
97
  def test_unix_socket
37
98
  path = '/tmp/test_unix_socket'
38
99
  FileUtils.rm(path) rescue nil
data/test/test_timer.rb CHANGED
@@ -11,7 +11,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
11
11
  @timer.stop
12
12
  end
13
13
 
14
- def test_move_on_after
14
+ def test_timer_move_on_after
15
15
  t0 = Time.now
16
16
  v = @timer.move_on_after(0.1) do
17
17
  sleep 1
@@ -23,7 +23,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
23
23
  assert_nil v
24
24
  end
25
25
 
26
- def test_move_on_after_with_value
26
+ def test_timer_move_on_after_with_value
27
27
  t0 = Time.now
28
28
  v = @timer.move_on_after(0.01, with_value: :bar) do
29
29
  sleep 1
@@ -35,7 +35,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
35
35
  assert_equal :bar, v
36
36
  end
37
37
 
38
- def test_move_on_after_with_reset
38
+ def test_timer_move_on_after_with_reset
39
39
  t0 = Time.now
40
40
  v = @timer.move_on_after(0.01, with_value: :moved_on) do
41
41
  sleep 0.007
@@ -48,7 +48,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
48
48
  t1 = Time.now
49
49
 
50
50
  assert_nil v
51
- assert_in_range 0.02..0.03, t1 - t0
51
+ assert_in_range 0.015..0.03, t1 - t0
52
52
  end
53
53
  end
54
54
 
@@ -61,7 +61,7 @@ class TimerCancelAfterTest < MiniTest::Test
61
61
  @timer.stop
62
62
  end
63
63
 
64
- def test_cancel_after
64
+ def test_timer_cancel_after
65
65
  t0 = Time.now
66
66
 
67
67
  assert_raises Polyphony::Cancel do
@@ -74,7 +74,7 @@ class TimerCancelAfterTest < MiniTest::Test
74
74
  assert_in_range 0.01..0.03, t1 - t0
75
75
  end
76
76
 
77
- def test_cancel_after_with_reset
77
+ def test_timer_cancel_after_with_reset
78
78
  t0 = Time.now
79
79
  @timer.cancel_after(0.01) do
80
80
  sleep 0.007
@@ -82,13 +82,13 @@ class TimerCancelAfterTest < MiniTest::Test
82
82
  sleep 0.007
83
83
  end
84
84
  t1 = Time.now
85
- assert_in_range 0.014..0.024, t1 - t0
85
+ assert_in_range 0.012..0.024, t1 - t0
86
86
  end
87
87
 
88
88
  class CustomException < Exception
89
89
  end
90
90
 
91
- def test_cancel_after_with_custom_exception
91
+ def test_timer_cancel_after_with_custom_exception
92
92
  assert_raises CustomException do
93
93
  @timer.cancel_after(0.01, with_exception: CustomException) do
94
94
  sleep 1
@@ -122,3 +122,36 @@ class TimerCancelAfterTest < MiniTest::Test
122
122
  end
123
123
  end
124
124
  end
125
+
126
+ class TimerMiscTest < MiniTest::Test
127
+ def setup
128
+ @timer = Polyphony::Timer.new(resolution: 0.001)
129
+ sleep 0
130
+ end
131
+
132
+ def teardown
133
+ @timer.stop
134
+ end
135
+
136
+ def test_timer_after
137
+ buffer = []
138
+ f = @timer.after(0.01) { buffer << 2 }
139
+ assert_kind_of Fiber, f
140
+ snooze
141
+ assert_equal [], buffer
142
+ sleep 0.1
143
+ p :post_sleep
144
+ assert_equal [2], buffer
145
+ end
146
+
147
+ def test_timer_every
148
+ buffer = []
149
+ t0 = Time.now
150
+ f = spin do
151
+ @timer.every(0.01) { buffer << 1 }
152
+ end
153
+ sleep 0.05
154
+ f.stop
155
+ assert_in_range 4..6, buffer.size
156
+ end
157
+ end