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
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,123 @@ 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
+ len = nil
245
+
246
+ spin {
247
+ len = o2.splice(i1, 1000)
248
+ o2.close
249
+ }
250
+
251
+ o1.write('foobar')
252
+ result = i2.read
253
+
254
+ assert_equal 'foobar', result
255
+ assert_equal 6, len
256
+ end
257
+
258
+ def test_splice_to_eof
259
+ i1, o1 = IO.pipe
260
+ i2, o2 = IO.pipe
261
+ len = nil
262
+
263
+ f = spin {
264
+ len = o2.splice_to_eof(i1, 1000)
265
+ o2.close
266
+ }
267
+
268
+ o1.write('foo')
269
+ result = i2.readpartial(1000)
270
+ assert_equal 'foo', result
271
+
272
+ o1.write('bar')
273
+ result = i2.readpartial(1000)
274
+ assert_equal 'bar', result
275
+ o1.close
276
+ f.await
277
+ assert_equal 6, len
278
+ ensure
279
+ if f.alive?
280
+ f.interrupt
281
+ f.await
282
+ end
283
+ end
284
+ end
285
+
286
+ class BackendChainTest < MiniTest::Test
287
+ def setup
288
+ super
289
+ @prev_backend = Thread.current.backend
290
+ @backend = Polyphony::Backend.new
291
+ Thread.current.backend = @backend
292
+ end
293
+
294
+ def teardown
295
+ @backend.finalize
296
+ Thread.current.backend = @prev_backend
297
+ end
298
+
299
+ def test_simple_write_chain
300
+ i, o = IO.pipe
301
+
302
+ result = Thread.backend.chain(
303
+ [:write, o, 'hello'],
304
+ [:write, o, ' world']
305
+ )
306
+
307
+ assert_equal 6, result
308
+ o.close
309
+ assert_equal 'hello world', i.read
310
+ end
311
+
312
+ def chunk_header(len)
313
+ "Content-Length: #{len}\r\n\r\n"
314
+ end
315
+
316
+ def serve_io(from, to)
317
+ i, o = IO.pipe
318
+ backend = Thread.current.backend
319
+ while true
320
+ len = o.splice(from, 8192)
321
+ break if len == 0
322
+
323
+ backend.chain(
324
+ [:write, to, chunk_header(len)],
325
+ [:splice, i, to, len]
326
+ )
327
+ end
328
+ to.close
329
+ end
330
+
331
+ def test_chain_with_splice
332
+ from_r, from_w = IO.pipe
333
+ to_r, to_w = IO.pipe
334
+
335
+ result = nil
336
+ f = spin { serve_io(from_r, to_w) }
337
+
338
+ from_w << 'Hello world!'
339
+ from_w.close
340
+
341
+ assert_equal "Content-Length: 12\r\n\r\nHello world!", to_r.read
342
+ end
343
+
344
+ def test_invalid_op
345
+ i, o = IO.pipe
346
+
347
+ assert_raises(RuntimeError) {
348
+ Thread.backend.chain(
349
+ [:read, o]
350
+ )
351
+ }
352
+
353
+ assert_raises(RuntimeError) {
354
+ Thread.backend.chain(
355
+ [:write, o]
356
+ )
357
+ }
358
+ end
212
359
  end
@@ -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
@@ -195,7 +195,7 @@ class IOTest < MiniTest::Test
195
195
  assert_equal ['foo', 'bar', 'baz'], buffer
196
196
  end
197
197
 
198
- class Receiver
198
+ class Receiver1
199
199
  attr_reader :buffer
200
200
 
201
201
  def initialize
@@ -209,7 +209,7 @@ class IOTest < MiniTest::Test
209
209
 
210
210
  def test_feed_loop_without_block
211
211
  i, o = IO.pipe
212
- receiver = Receiver.new
212
+ receiver = Receiver1.new
213
213
  reader = spin do
214
214
  i.feed_loop(receiver, :recv)
215
215
  end
@@ -225,6 +225,37 @@ class IOTest < MiniTest::Test
225
225
  sleep 0.01
226
226
  assert_equal ['foo', 'bar', 'baz'], receiver.buffer
227
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
228
259
  end
229
260
 
230
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
@@ -34,6 +34,33 @@ class SocketTest < MiniTest::Test
34
34
  server&.close
35
35
  end
36
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
+
37
64
  def test_feed_loop
38
65
  port = rand(1234..5678)
39
66
  server = TCPServer.new('127.0.0.1', port)
data/test/test_sync.rb CHANGED
@@ -70,4 +70,47 @@ class MutexTest < MiniTest::Test
70
70
  Fiber.current.await_all_children
71
71
  assert_equal [:bar, :foo], buf
72
72
  end
73
+
74
+ def test_owned?
75
+ buf = []
76
+ lock = Polyphony::Mutex.new
77
+ (1..3).each do |i|
78
+ spin do
79
+ lock.synchronize do
80
+ buf << ">> #{i}"
81
+ buf << [i, lock.owned?]
82
+ sleep(rand * 0.05)
83
+ buf << "<< #{i}"
84
+ end
85
+ buf << [i, lock.owned?]
86
+ end
87
+ end
88
+
89
+ Fiber.current.await_all_children
90
+ assert_equal ['>> 1', [1, true], '<< 1', [1, false], '>> 2', [2, true], '<< 2', [2, false], '>> 3', [3, true], '<< 3', [3, false]], buf
91
+ end
92
+
93
+ def test_locked?
94
+ lock = Polyphony::Mutex.new
95
+ a = spin do
96
+ sender = receive
97
+ lock.synchronize do
98
+ sender << 'pong'
99
+ receive
100
+ end
101
+ sender << 'pong'
102
+ end
103
+
104
+ snooze
105
+ assert !lock.locked?
106
+ a << Fiber.current
107
+
108
+ receive
109
+ assert lock.locked?
110
+
111
+ a << Fiber.current
112
+
113
+ receive
114
+ assert !lock.locked?
115
+ end
73
116
  end
data/test/test_thread.rb CHANGED
@@ -124,6 +124,10 @@ class ThreadTest < MiniTest::Test
124
124
  t&.join
125
125
  end
126
126
 
127
+ def test_backend_class_method
128
+ assert_equal Thread.current.backend, Thread.backend
129
+ end
130
+
127
131
  def test_that_suspend_returns_immediately_if_no_watchers
128
132
  records = []
129
133
  t = Polyphony::Trace.new(:fiber_all) do |r|
data/test/test_timer.rb CHANGED
@@ -82,7 +82,7 @@ class TimerCancelAfterTest < MiniTest::Test
82
82
  sleep 0.007
83
83
  end
84
84
  t1 = Time.now
85
- assert_in_range 0.013..0.024, t1 - t0
85
+ assert_in_range 0.012..0.024, t1 - t0
86
86
  end
87
87
 
88
88
  class CustomException < Exception
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.51.0
4
+ version: 0.54.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-02 00:00:00.000000000 Z
11
+ date: 2021-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 5.13.0
33
+ version: 5.14.4
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 5.13.0
40
+ version: 5.14.4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest-reporters
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -227,61 +227,19 @@ dependencies:
227
227
  - !ruby/object:Gem::Version
228
228
  version: 0.17.1
229
229
  - !ruby/object:Gem::Dependency
230
- name: jekyll
230
+ name: localhost
231
231
  requirement: !ruby/object:Gem::Requirement
232
232
  requirements:
233
233
  - - "~>"
234
234
  - !ruby/object:Gem::Version
235
- version: 3.8.6
236
- type: :development
237
- prerelease: false
238
- version_requirements: !ruby/object:Gem::Requirement
239
- requirements:
240
- - - "~>"
241
- - !ruby/object:Gem::Version
242
- version: 3.8.6
243
- - !ruby/object:Gem::Dependency
244
- name: jekyll-remote-theme
245
- requirement: !ruby/object:Gem::Requirement
246
- requirements:
247
- - - "~>"
248
- - !ruby/object:Gem::Version
249
- version: 0.4.1
250
- type: :development
251
- prerelease: false
252
- version_requirements: !ruby/object:Gem::Requirement
253
- requirements:
254
- - - "~>"
255
- - !ruby/object:Gem::Version
256
- version: 0.4.1
257
- - !ruby/object:Gem::Dependency
258
- name: jekyll-seo-tag
259
- requirement: !ruby/object:Gem::Requirement
260
- requirements:
261
- - - "~>"
262
- - !ruby/object:Gem::Version
263
- version: 2.6.1
264
- type: :development
265
- prerelease: false
266
- version_requirements: !ruby/object:Gem::Requirement
267
- requirements:
268
- - - "~>"
269
- - !ruby/object:Gem::Version
270
- version: 2.6.1
271
- - !ruby/object:Gem::Dependency
272
- name: just-the-docs
273
- requirement: !ruby/object:Gem::Requirement
274
- requirements:
275
- - - "~>"
276
- - !ruby/object:Gem::Version
277
- version: 0.3.0
235
+ version: 1.1.4
278
236
  type: :development
279
237
  prerelease: false
280
238
  version_requirements: !ruby/object:Gem::Requirement
281
239
  requirements:
282
240
  - - "~>"
283
241
  - !ruby/object:Gem::Version
284
- version: 0.3.0
242
+ version: 1.1.4
285
243
  description:
286
244
  email: sharon@noteflakes.com
287
245
  executables: []
@@ -369,6 +327,7 @@ files:
369
327
  - examples/core/interrupt.rb
370
328
  - examples/core/nested.rb
371
329
  - examples/core/pingpong.rb
330
+ - examples/core/queue.rb
372
331
  - examples/core/recurrent-timer.rb
373
332
  - examples/core/resource_delegate.rb
374
333
  - examples/core/spin.rb
@@ -392,6 +351,7 @@ files:
392
351
  - examples/io/echo_stdin.rb
393
352
  - examples/io/happy-eyeballs.rb
394
353
  - examples/io/httparty.rb
354
+ - examples/io/https_server.rb
395
355
  - examples/io/irb.rb
396
356
  - examples/io/net-http.rb
397
357
  - examples/io/open.rb
@@ -474,6 +434,7 @@ files:
474
434
  - ext/polyphony/runqueue.c
475
435
  - ext/polyphony/runqueue_ring_buffer.c
476
436
  - ext/polyphony/runqueue_ring_buffer.h
437
+ - ext/polyphony/socket_extensions.c
477
438
  - ext/polyphony/thread.c
478
439
  - ext/polyphony/tracing.c
479
440
  - lib/polyphony.rb