polyphony 0.45.2 → 0.47.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +39 -0
  5. data/Gemfile.lock +3 -3
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +20 -28
  9. data/bin/test +4 -0
  10. data/examples/core/enumerable.rb +64 -0
  11. data/examples/io/raw.rb +14 -0
  12. data/examples/io/reline.rb +18 -0
  13. data/examples/performance/fiber_resume.rb +43 -0
  14. data/examples/performance/fiber_transfer.rb +13 -4
  15. data/examples/performance/multi_snooze.rb +0 -1
  16. data/examples/performance/thread-vs-fiber/compare.rb +59 -0
  17. data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
  19. data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
  20. data/examples/performance/thread_switch.rb +44 -0
  21. data/ext/liburing/liburing.h +585 -0
  22. data/ext/liburing/liburing/README.md +4 -0
  23. data/ext/liburing/liburing/barrier.h +73 -0
  24. data/ext/liburing/liburing/compat.h +15 -0
  25. data/ext/liburing/liburing/io_uring.h +343 -0
  26. data/ext/liburing/queue.c +333 -0
  27. data/ext/liburing/register.c +187 -0
  28. data/ext/liburing/setup.c +210 -0
  29. data/ext/liburing/syscall.c +54 -0
  30. data/ext/liburing/syscall.h +18 -0
  31. data/ext/polyphony/backend.h +1 -15
  32. data/ext/polyphony/backend_common.h +129 -0
  33. data/ext/polyphony/backend_io_uring.c +995 -0
  34. data/ext/polyphony/backend_io_uring_context.c +74 -0
  35. data/ext/polyphony/backend_io_uring_context.h +53 -0
  36. data/ext/polyphony/{libev_backend.c → backend_libev.c} +308 -297
  37. data/ext/polyphony/event.c +1 -1
  38. data/ext/polyphony/extconf.rb +31 -13
  39. data/ext/polyphony/fiber.c +60 -32
  40. data/ext/polyphony/libev.c +4 -0
  41. data/ext/polyphony/libev.h +8 -2
  42. data/ext/polyphony/liburing.c +8 -0
  43. data/ext/polyphony/playground.c +51 -0
  44. data/ext/polyphony/polyphony.c +9 -6
  45. data/ext/polyphony/polyphony.h +35 -19
  46. data/ext/polyphony/polyphony_ext.c +12 -4
  47. data/ext/polyphony/queue.c +100 -35
  48. data/ext/polyphony/runqueue.c +102 -0
  49. data/ext/polyphony/runqueue_ring_buffer.c +85 -0
  50. data/ext/polyphony/runqueue_ring_buffer.h +31 -0
  51. data/ext/polyphony/thread.c +42 -90
  52. data/lib/polyphony.rb +2 -2
  53. data/lib/polyphony/adapters/process.rb +0 -3
  54. data/lib/polyphony/adapters/trace.rb +2 -2
  55. data/lib/polyphony/core/exceptions.rb +0 -4
  56. data/lib/polyphony/core/global_api.rb +47 -23
  57. data/lib/polyphony/core/sync.rb +7 -5
  58. data/lib/polyphony/extensions/core.rb +14 -33
  59. data/lib/polyphony/extensions/debug.rb +13 -0
  60. data/lib/polyphony/extensions/fiber.rb +21 -3
  61. data/lib/polyphony/extensions/io.rb +15 -4
  62. data/lib/polyphony/extensions/openssl.rb +6 -0
  63. data/lib/polyphony/extensions/socket.rb +63 -10
  64. data/lib/polyphony/version.rb +1 -1
  65. data/polyphony.gemspec +1 -1
  66. data/test/helper.rb +36 -4
  67. data/test/io_uring_test.rb +55 -0
  68. data/test/stress.rb +4 -1
  69. data/test/test_backend.rb +63 -6
  70. data/test/test_ext.rb +1 -2
  71. data/test/test_fiber.rb +55 -20
  72. data/test/test_global_api.rb +132 -31
  73. data/test/test_io.rb +42 -0
  74. data/test/test_queue.rb +117 -0
  75. data/test/test_signal.rb +11 -8
  76. data/test/test_socket.rb +2 -2
  77. data/test/test_sync.rb +21 -0
  78. data/test/test_throttler.rb +3 -6
  79. data/test/test_trace.rb +7 -5
  80. metadata +36 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2fa4fa731ff52272d3aed456f7a26751e76c63b4335366919bffaf089bf96dd
4
- data.tar.gz: 1afe67258c5c73d9cc1082ca238eb358dac0f5b7a975f90a77b4f6de6a23b2a4
3
+ metadata.gz: 606d74c424178b9e8bb17cd7b2848e0e1c1b092e77eeb98ce1026fd7d1f38c61
4
+ data.tar.gz: 9e003833af5f2c8505ea41d2ebe728fbf776f428f47d925b0185fd592d102737
5
5
  SHA512:
6
- metadata.gz: 175edf315d759f85d2c0d934be36b9fcacd68342206d984ad64f800120a8b65668e9ea81595f47cbbb439402c256b47fde78f0cdad4b42e0c3b1d6ee321135b9
7
- data.tar.gz: 01cfe1d8b51d60a736adb80062eb22e7983a1879a5491cb7a2e47ec4ad5734e0c8bd8664653870a4393276b42fa912953721658ecff634521a672419c8d3cab6
6
+ metadata.gz: 1cfb1c81d3b38a545ceb856d4619c11386407081ecd43a6e6209d2ed4efb1c436242aa016657d379f0a0331ca24944f5e51c5359ad44afc3a88d53af1de7976b
7
+ data.tar.gz: 9b47ba16c7de942bea952f9f12d225aed86a505c2fd6cf7740a88d5d398af0e91e37a16e3ee9af2129ad88c8ff17c2b5aba7dcf6630811af62202e4b2585d6dc
@@ -23,6 +23,8 @@ jobs:
23
23
  run: |
24
24
  gem install bundler
25
25
  bundle install
26
+ - name: Show Linux kernel version
27
+ run: uname -r
26
28
  - name: Compile C-extension
27
29
  run: bundle exec rake compile
28
30
  - name: Run tests
File without changes
@@ -1,3 +1,42 @@
1
+ ## 0.47.0
2
+
3
+ * Implement `#spin_scope` used for creating blocking fiber scopes
4
+ * Reimplement `move_on_after`, `cancel_after`, `Timeout.timeout` using
5
+ `Backend#timeout` (avoids creating canceller fiber for most common use case)
6
+ * Implement `Backend#timeout` API
7
+ * Implemented capped queues
8
+
9
+ ## 0.46.1
10
+
11
+ * Add `TCPServer#accept_loop`, `OpenSSL::SSL::SSLSocket#accept_loop` method
12
+ * Fix compilation error on MacOS (#43)
13
+ * Fix backtrace for `Timeout.timeout`
14
+ * Add `Backend#timer_loop`
15
+
16
+ ## 0.46.0
17
+
18
+ * Implement [io_uring backend](https://github.com/digital-fabric/polyphony/pull/44)
19
+
20
+ ## 0.45.5
21
+
22
+ * Fix compilation error (#43)
23
+ * Add support for resetting move_on_after, cancel_after timeouts
24
+ * Optimize anti-event starvation polling
25
+ * Implement optimized runqueue for better performance
26
+ * Schedule parent with priority on uncaught exception
27
+ * Fix race condition in `Mutex#synchronize` (#41)
28
+
29
+ ## 0.45.4
30
+
31
+ * Improve signal trapping mechanism
32
+
33
+ ## 0.45.3
34
+
35
+ * Don't swallow error in `Process#kill_and_await`
36
+ * Add `Fiber#mailbox` attribute reader
37
+ * Fix bug in `Fiber.await`
38
+ * Implement `IO#getc`, `IO#getbyte`
39
+
1
40
  ## 0.45.2
2
41
 
3
42
  * Rewrite `Fiber#<<`, `Fiber#await`, `Fiber#receive` in C
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.45.2)
4
+ polyphony (0.47.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -87,7 +87,7 @@ GEM
87
87
  rack (2.2.3)
88
88
  rainbow (3.0.0)
89
89
  rake (12.3.3)
90
- rake-compiler (1.0.5)
90
+ rake-compiler (1.1.1)
91
91
  rake
92
92
  rb-fsevent (0.10.3)
93
93
  rb-inotify (0.10.1)
@@ -141,7 +141,7 @@ DEPENDENCIES
141
141
  polyphony!
142
142
  pry (= 0.13.1)
143
143
  rack (>= 2.0.8, < 2.3.0)
144
- rake-compiler (= 1.0.5)
144
+ rake-compiler (= 1.1.1)
145
145
  redis (= 4.1.0)
146
146
  rubocop (= 0.85.1)
147
147
  sequel (= 5.34.0)
data/README.md CHANGED
@@ -35,9 +35,9 @@
35
35
  Polyphony is a library for building concurrent applications in Ruby. Polyphony
36
36
  harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html)
37
37
  to provide a cooperative, sequential coroutine-based concurrency model. Under
38
- the hood, Polyphony uses [libev](https://github.com/enki/libev) as a
39
- high-performance event reactor that provides timers, I/O watchers and other
40
- asynchronous event primitives.
38
+ the hood, Polyphony uses
39
+ [io_uring](https://unixism.net/loti/what_is_io_uring.html) or
40
+ [libev](https://github.com/enki/libev) to maximize I/O performance.
41
41
 
42
42
  ## Features
43
43
 
data/Rakefile CHANGED
@@ -23,4 +23,4 @@ task :docs do
23
23
  exec 'RUBYOPT=-W0 jekyll serve -s docs -H ec2-18-156-117-172.eu-central-1.compute.amazonaws.com'
24
24
  end
25
25
 
26
- CLEAN.include "**/*.o", "**/*.so", "**/*.bundle", "**/*.jar", "pkg", "tmp"
26
+ CLEAN.include "**/*.o", "**/*.so", "**/*.so.*", "**/*.a", "**/*.bundle", "**/*.jar", "pkg", "tmp"
data/TODO.md CHANGED
@@ -1,15 +1,24 @@
1
- 0.45.2
1
+ ## Roadmap for Polyphony 1.0
2
2
 
3
- - Adapter for Pry and IRB (Which fixes #5 and #6)
4
- - Redesign signal handling - the current mechanism is problematic in that it
5
- does not address signals that do not kill, for instance HUP or USR1.
3
+ - Check why worker-thread example doesn't work.
4
+ - Add test that mimics the original design for Monocrono:
5
+ - 256 fibers each waiting for a message
6
+ - When message received do some blocking work using a `ThreadPool`
7
+ - Send messages, collect responses, check for correctness
6
8
  - Improve `#supervise`. It does not work as advertised, and seems to exhibit an
7
9
  inconsistent behaviour (see supervisor example).
8
- - Fix backtrace for `Timeout.timeout` API (see timeout example).
9
- - Check why worker-thread example doesn't work.
10
10
 
11
- 0.46.0
11
+ - io_uring
12
+ - Use playground.c to find out why we when submitting and waiting for
13
+ completion in single syscall signals seem to be blocked until the syscall
14
+ returns. Is this a bug in io_uring/liburing?
15
+
16
+ -----------------------------------------------------
12
17
 
18
+ - Add `Backend#splice(in, out, nbytes)` API
19
+ - Adapter for io/console (what does `IO#raw` do?)
20
+ - Adapter for Pry and IRB (Which fixes #5 and #6)
21
+ - allow backend selection at runtime
13
22
  - Debugging
14
23
  - Eat your own dogfood: need a good tool to check what's going on when some
15
24
  test fails
@@ -122,8 +131,6 @@
122
131
  - discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
123
132
 
124
133
 
125
- ## 0.47
126
-
127
134
  ### Some more API work, more docs
128
135
 
129
136
  - sintra app with database access (postgresql)
@@ -135,14 +142,10 @@
135
142
  - proceed from there
136
143
 
137
144
 
138
- ## 0.48
139
-
140
145
  ### Sinatra / Sidekiq
141
146
 
142
147
  - Pull out redis/postgres code, put into new `polyphony-xxx` gems
143
148
 
144
- ## 0.49
145
-
146
149
  ### Testing && Docs
147
150
 
148
151
  - More tests
@@ -153,7 +156,10 @@
153
156
  - `IO.foreach`
154
157
  - `Process.waitpid`
155
158
 
156
- ## 0.50 DNS
159
+ ### Quic / HTTP/3
160
+
161
+ - Python impl: https://github.com/aiortc/aioquic/
162
+ - Go impl: https://github.com/lucas-clemente/quic-go
157
163
 
158
164
  ### DNS client
159
165
 
@@ -186,17 +192,3 @@ Prior art:
186
192
 
187
193
  - https://github.com/socketry/async-dns
188
194
 
189
- ## Work on API
190
-
191
- - Add option for setting the exception raised on cancelling using `#cancel_after`:
192
-
193
- ```ruby
194
- cancel_after(3, with_error: MyErrorClass) do
195
- do_my_thing
196
- end
197
- # or a RuntimeError with message
198
- cancel_after(3, with_error: 'Cancelled due to timeout') do
199
- do_my_thing
200
- end
201
- ```
202
-
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ clear && POLYPHONY_USE_LIBEV=1 rake recompile && ruby test/run.rb
4
+ clear && rake recompile && ruby test/run.rb
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ module Enumerable
9
+ def map_concurrently(&block)
10
+ spin do
11
+ results = []
12
+ each_with_index do |i, idx|
13
+ spin { results[idx] = block.(i) }
14
+ end
15
+ Fiber.current.await_all_children
16
+ results
17
+ end.await
18
+ end
19
+
20
+ def each_concurrently(max_fibers: nil, &block)
21
+ return each_concurrently_with_fiber_pool(max_fibers, &block) if max_fibers
22
+
23
+ spin do
24
+ results = []
25
+ each do |i|
26
+ spin(&block).schedule(i)
27
+ end
28
+ Fiber.current.await_all_children
29
+ end.await
30
+ self
31
+ end
32
+
33
+ def each_concurrently_with_fiber_pool(max_fibers, &block)
34
+ spin do
35
+ fiber_count = 0
36
+ workers = []
37
+ each do |i|
38
+ if fiber_count < max_fibers
39
+ workers << spin do
40
+ loop do
41
+ item = receive
42
+ break if item == :__stop__
43
+ block.(item)
44
+ end
45
+ end
46
+ end
47
+
48
+ fiber = workers.shift
49
+ fiber << i
50
+ workers << fiber
51
+ end
52
+ workers.each { |f| f << :__stop__ }
53
+ Fiber.current.await_all_children
54
+ end.await
55
+ self
56
+ end
57
+ end
58
+
59
+ o = 1..3
60
+ o.each_concurrently(max_fibers: 2) do |i|
61
+ puts "#{Fiber.current} sleep #{i}"
62
+ sleep(i)
63
+ puts "wakeup #{i}"
64
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'io/console'
6
+
7
+ c = STDIN.raw(min: 1, tim: 0, &:getbyte)
8
+ p result: c
9
+ exit
10
+
11
+ puts '?' * 40
12
+ c = STDIN.getbyte
13
+ puts '*' * 40
14
+ p c
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'polyphony/adapters/readline'
6
+ require 'pry'
7
+
8
+ $counter = 0
9
+ timer = spin do
10
+ throttled_loop(5) do
11
+ $counter += 1
12
+ end
13
+ end
14
+
15
+ at_exit { timer.stop }
16
+
17
+ puts 'try typing $counter to see the counter incremented in the background'
18
+ binding.pry
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fiber'
4
+
5
+ class Fiber
6
+ attr_accessor :next
7
+ end
8
+
9
+ # This program shows how the performance of Fiber.transfer degrades as the fiber
10
+ # count increases
11
+
12
+ def run(num_fibers)
13
+ count = 0
14
+
15
+ GC.start
16
+ GC.disable
17
+
18
+ fibers = []
19
+ num_fibers.times do
20
+ fibers << Fiber.new { loop { Fiber.yield } }
21
+ end
22
+
23
+ t0 = Time.now
24
+
25
+ while count < 1000000
26
+ fibers.each do |f|
27
+ count += 1
28
+ f.resume
29
+ end
30
+ end
31
+
32
+ elapsed = Time.now - t0
33
+
34
+ puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
35
+ rescue Exception => e
36
+ puts "Stopped at #{count} fibers"
37
+ p e
38
+ end
39
+
40
+ run(100)
41
+ run(1000)
42
+ run(10000)
43
+ run(100000)
@@ -12,6 +12,7 @@ end
12
12
  def run(num_fibers)
13
13
  count = 0
14
14
 
15
+ GC.start
15
16
  GC.disable
16
17
 
17
18
  first = nil
@@ -36,13 +37,21 @@ def run(num_fibers)
36
37
  last.next = first
37
38
 
38
39
  t0 = Time.now
40
+ puts "start transfer..."
39
41
  first.transfer
40
42
  elapsed = Time.now - t0
41
43
 
42
- puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
43
- GC.start
44
+ rss = `ps -o rss= -p #{Process.pid}`.to_i
45
+
46
+ puts "fibers: #{num_fibers} rss: #{rss} count: #{count} rate: #{count / elapsed}"
47
+ rescue Exception => e
48
+ puts "Stopped at #{count} fibers"
49
+ p e
44
50
  end
45
51
 
52
+ puts "pid: #{Process.pid}"
46
53
  run(100)
47
- run(1000)
48
- run(10000)
54
+ # run(1000)
55
+ # run(10000)
56
+ # run(100000)
57
+ # run(400000)
@@ -18,7 +18,6 @@ def bm(fibers, iterations)
18
18
  Fiber.current.await_all_children
19
19
  dt = Time.now - t0
20
20
  puts "#{[fibers, iterations].inspect} setup: #{t0 - t_pre}s count: #{count} #{count / dt.to_f}/s"
21
- Thread.current.run_queue_trace
22
21
  end
23
22
 
24
23
  GC.disable
@@ -0,0 +1,59 @@
1
+ SERVERS = {
2
+ polyphony: {
3
+ port: 1234,
4
+ cmd: 'ruby examples/performance/thread-vs-fiber/polyphony_server.rb'
5
+ },
6
+ threaded: {
7
+ port: 1235,
8
+ cmd: 'ruby examples/performance/thread-vs-fiber/threaded_server.rb'
9
+ },
10
+ em: {
11
+ port: 1236,
12
+ cmd: 'ruby examples/performance/thread-vs-fiber/em_server.rb'
13
+ }
14
+ }
15
+ SETTINGS = [
16
+ '-t1 -c1',
17
+ '-t4 -c8',
18
+ '-t8 -c64',
19
+ '-t16 -c512',
20
+ '-t32 -c4096',
21
+ '-t64 -c8192',
22
+ '-t128 -c16384',
23
+ '-t256 -c32768'
24
+ ]
25
+
26
+ def run_test(name, port, cmd, setting)
27
+ puts "*" * 80
28
+ puts "Run #{name} (#{port}): #{setting}"
29
+ puts "*" * 80
30
+
31
+ pid = spawn("#{cmd} > /dev/null 2>&1")
32
+ sleep 1
33
+
34
+ output = `wrk -d60 #{setting} \"http://127.0.0.1:#{port}/\"`
35
+ puts output
36
+ (output =~ /Requests\/sec:\s+(\d+)/) && $1.to_i
37
+ ensure
38
+ Process.kill('KILL', pid)
39
+ Process.wait(pid)
40
+ 3.times { puts }
41
+ end
42
+
43
+ def perform_benchmark
44
+ results = []
45
+ SETTINGS.each do |s|
46
+ results << SERVERS.inject({}) do |h, (n, o)|
47
+ h[n] = run_test(n, o[:port], o[:cmd], s)
48
+ h
49
+ end
50
+ end
51
+ results
52
+ end
53
+
54
+ results = []
55
+ 3.times { results << perform_benchmark }
56
+
57
+ require 'pp'
58
+ puts "results:"
59
+ pp results