polyphony 0.45.4 → 0.47.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -0
- data/.gitmodules +0 -0
- data/CHANGELOG.md +32 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/TODO.md +20 -34
- data/bin/test +4 -0
- data/examples/core/enumerable.rb +64 -0
- data/examples/performance/fiber_resume.rb +43 -0
- data/examples/performance/fiber_transfer.rb +13 -4
- data/examples/performance/multi_snooze.rb +0 -1
- data/examples/performance/thread-vs-fiber/compare.rb +59 -0
- data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
- data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
- data/examples/performance/thread_switch.rb +44 -0
- data/ext/liburing/liburing.h +585 -0
- data/ext/liburing/liburing/README.md +4 -0
- data/ext/liburing/liburing/barrier.h +73 -0
- data/ext/liburing/liburing/compat.h +15 -0
- data/ext/liburing/liburing/io_uring.h +343 -0
- data/ext/liburing/queue.c +333 -0
- data/ext/liburing/register.c +187 -0
- data/ext/liburing/setup.c +210 -0
- data/ext/liburing/syscall.c +54 -0
- data/ext/liburing/syscall.h +18 -0
- data/ext/polyphony/backend.h +1 -15
- data/ext/polyphony/backend_common.h +129 -0
- data/ext/polyphony/backend_io_uring.c +995 -0
- data/ext/polyphony/backend_io_uring_context.c +74 -0
- data/ext/polyphony/backend_io_uring_context.h +53 -0
- data/ext/polyphony/{libev_backend.c → backend_libev.c} +308 -297
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/extconf.rb +31 -13
- data/ext/polyphony/fiber.c +60 -32
- data/ext/polyphony/libev.c +4 -0
- data/ext/polyphony/libev.h +8 -2
- data/ext/polyphony/liburing.c +8 -0
- data/ext/polyphony/playground.c +51 -0
- data/ext/polyphony/polyphony.c +9 -6
- data/ext/polyphony/polyphony.h +35 -19
- data/ext/polyphony/polyphony_ext.c +12 -4
- data/ext/polyphony/queue.c +100 -35
- data/ext/polyphony/runqueue.c +102 -0
- data/ext/polyphony/runqueue_ring_buffer.c +85 -0
- data/ext/polyphony/runqueue_ring_buffer.h +31 -0
- data/ext/polyphony/thread.c +42 -90
- data/lib/polyphony/adapters/trace.rb +2 -2
- data/lib/polyphony/core/exceptions.rb +0 -4
- data/lib/polyphony/core/global_api.rb +47 -23
- data/lib/polyphony/core/resource_pool.rb +12 -1
- data/lib/polyphony/core/sync.rb +7 -5
- data/lib/polyphony/extensions/core.rb +9 -15
- data/lib/polyphony/extensions/debug.rb +13 -0
- data/lib/polyphony/extensions/fiber.rb +13 -9
- data/lib/polyphony/extensions/openssl.rb +6 -0
- data/lib/polyphony/extensions/socket.rb +68 -10
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +36 -4
- data/test/io_uring_test.rb +55 -0
- data/test/stress.rb +4 -1
- data/test/test_backend.rb +63 -6
- data/test/test_ext.rb +1 -2
- data/test/test_fiber.rb +55 -20
- data/test/test_global_api.rb +132 -31
- data/test/test_queue.rb +117 -0
- data/test/test_resource_pool.rb +21 -0
- data/test/test_socket.rb +2 -2
- data/test/test_sync.rb +21 -0
- data/test/test_throttler.rb +3 -6
- data/test/test_trace.rb +7 -5
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9b7b3885b4f6d88b63d29814e7ce7883927c58fefaacab3ef980dafb3ef380f
|
4
|
+
data.tar.gz: 14ee82740f415364b675db77f9246482937a2650df437ecf42c8d590bbc3d9fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98d245d285e39383c2fad4f3045b2b994922f9c0c1100aec7b264c731cba9a433e8e0677a173bbbb3cfc11199e9f5f29d98c55ebf7224a5bb3358db93ba25bd3
|
7
|
+
data.tar.gz: 6bfa625ff66461d4a23b8e5c6c8d9c1818b6030950da28e7712d3207ee11654527b36205ddab904bfed58a85144b3f73e7892479992eb775bfe7fcc1d4e54ac0
|
data/.github/workflows/test.yml
CHANGED
data/.gitmodules
ADDED
File without changes
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
## 0.47.1
|
2
|
+
|
3
|
+
* Fix API compatibility between TCPSocket and IO
|
4
|
+
|
5
|
+
## 0.47.0
|
6
|
+
|
7
|
+
* Implement `#spin_scope` used for creating blocking fiber scopes
|
8
|
+
* Reimplement `move_on_after`, `cancel_after`, `Timeout.timeout` using
|
9
|
+
`Backend#timeout` (avoids creating canceller fiber for most common use case)
|
10
|
+
* Implement `Backend#timeout` API
|
11
|
+
* Implemented capped queues
|
12
|
+
|
13
|
+
## 0.46.1
|
14
|
+
|
15
|
+
* Add `TCPServer#accept_loop`, `OpenSSL::SSL::SSLSocket#accept_loop` method
|
16
|
+
* Fix compilation error on MacOS (#43)
|
17
|
+
* Fix backtrace for `Timeout.timeout`
|
18
|
+
* Add `Backend#timer_loop`
|
19
|
+
|
20
|
+
## 0.46.0
|
21
|
+
|
22
|
+
* Implement [io_uring backend](https://github.com/digital-fabric/polyphony/pull/44)
|
23
|
+
|
24
|
+
## 0.45.5
|
25
|
+
|
26
|
+
* Fix compilation error (#43)
|
27
|
+
* Add support for resetting move_on_after, cancel_after timeouts
|
28
|
+
* Optimize anti-event starvation polling
|
29
|
+
* Implement optimized runqueue for better performance
|
30
|
+
* Schedule parent with priority on uncaught exception
|
31
|
+
* Fix race condition in `Mutex#synchronize` (#41)
|
32
|
+
|
1
33
|
## 0.45.4
|
2
34
|
|
3
35
|
* Improve signal trapping mechanism
|
data/Gemfile.lock
CHANGED
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
|
39
|
-
|
40
|
-
|
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,21 +1,24 @@
|
|
1
|
-
|
2
|
-
io_uring: some work has been done on an io_uring based scheduler here:
|
3
|
-
https://github.com/dsh0416/evt
|
4
|
-
|
5
|
-
This can serve as a starting point for doing stuff with io_uring
|
6
|
-
)
|
1
|
+
## Roadmap for Polyphony 1.0
|
7
2
|
|
8
|
-
|
9
|
-
|
10
|
-
-
|
11
|
-
-
|
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
|
12
8
|
- Improve `#supervise`. It does not work as advertised, and seems to exhibit an
|
13
9
|
inconsistent behaviour (see supervisor example).
|
14
|
-
- Fix backtrace for `Timeout.timeout` API (see timeout example).
|
15
|
-
- Check why worker-thread example doesn't work.
|
16
10
|
|
17
|
-
|
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?
|
18
15
|
|
16
|
+
-----------------------------------------------------
|
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
|
19
22
|
- Debugging
|
20
23
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
21
24
|
test fails
|
@@ -128,8 +131,6 @@
|
|
128
131
|
- discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
|
129
132
|
|
130
133
|
|
131
|
-
## 0.47
|
132
|
-
|
133
134
|
### Some more API work, more docs
|
134
135
|
|
135
136
|
- sintra app with database access (postgresql)
|
@@ -141,14 +142,10 @@
|
|
141
142
|
- proceed from there
|
142
143
|
|
143
144
|
|
144
|
-
## 0.48
|
145
|
-
|
146
145
|
### Sinatra / Sidekiq
|
147
146
|
|
148
147
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
149
148
|
|
150
|
-
## 0.49
|
151
|
-
|
152
149
|
### Testing && Docs
|
153
150
|
|
154
151
|
- More tests
|
@@ -159,7 +156,10 @@
|
|
159
156
|
- `IO.foreach`
|
160
157
|
- `Process.waitpid`
|
161
158
|
|
162
|
-
|
159
|
+
### Quic / HTTP/3
|
160
|
+
|
161
|
+
- Python impl: https://github.com/aiortc/aioquic/
|
162
|
+
- Go impl: https://github.com/lucas-clemente/quic-go
|
163
163
|
|
164
164
|
### DNS client
|
165
165
|
|
@@ -192,17 +192,3 @@ Prior art:
|
|
192
192
|
|
193
193
|
- https://github.com/socketry/async-dns
|
194
194
|
|
195
|
-
## Work on API
|
196
|
-
|
197
|
-
- Add option for setting the exception raised on cancelling using `#cancel_after`:
|
198
|
-
|
199
|
-
```ruby
|
200
|
-
cancel_after(3, with_error: MyErrorClass) do
|
201
|
-
do_my_thing
|
202
|
-
end
|
203
|
-
# or a RuntimeError with message
|
204
|
-
cancel_after(3, with_error: 'Cancelled due to timeout') do
|
205
|
-
do_my_thing
|
206
|
-
end
|
207
|
-
```
|
208
|
-
|
data/bin/test
ADDED
@@ -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,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
|
-
|
43
|
-
|
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)
|
@@ -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
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'http/parser'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module HTTPServer
|
6
|
+
def post_init
|
7
|
+
@parser = Http::Parser.new
|
8
|
+
@pending_requests = []
|
9
|
+
@parser.on_message_complete = proc { @pending_requests << @parser }
|
10
|
+
end
|
11
|
+
|
12
|
+
def receive_data(data)
|
13
|
+
@parser << data
|
14
|
+
write_response while @pending_requests.shift
|
15
|
+
end
|
16
|
+
|
17
|
+
def write_response
|
18
|
+
status_code = "200 OK"
|
19
|
+
data = "Hello world!\n"
|
20
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
|
21
|
+
send_data "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
EventMachine::run do
|
26
|
+
EventMachine::start_server(
|
27
|
+
'0.0.0.0',
|
28
|
+
1236,
|
29
|
+
HTTPServer
|
30
|
+
)
|
31
|
+
puts "pid #{Process.pid} EventMachine listening on port 1236"
|
32
|
+
|
33
|
+
end
|
@@ -4,42 +4,31 @@ require 'bundler/setup'
|
|
4
4
|
require 'polyphony'
|
5
5
|
require 'http/parser'
|
6
6
|
|
7
|
-
$connection_count = 0
|
8
|
-
|
9
7
|
def handle_client(socket)
|
10
|
-
|
8
|
+
pending_requests = []
|
11
9
|
parser = Http::Parser.new
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
socket.read_loop do |data|
|
10
|
+
parser.on_message_complete = proc { pending_requests << parser }
|
11
|
+
|
12
|
+
socket.recv_loop do |data|
|
17
13
|
parser << data
|
18
|
-
|
19
|
-
handle_request(socket, req)
|
20
|
-
req = nil
|
21
|
-
snooze
|
22
|
-
end
|
14
|
+
write_response(socket) while pending_requests.shift
|
23
15
|
end
|
24
16
|
rescue IOError, SystemCallError => e
|
25
17
|
# do nothing
|
26
18
|
ensure
|
27
|
-
$connection_count -= 1
|
28
19
|
socket&.close
|
29
20
|
end
|
30
21
|
|
31
|
-
def
|
22
|
+
def write_response(socket)
|
32
23
|
status_code = "200 OK"
|
33
24
|
data = "Hello world!\n"
|
34
25
|
headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
|
35
|
-
|
26
|
+
socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
36
27
|
end
|
37
28
|
|
38
29
|
server = TCPServer.open('0.0.0.0', 1234)
|
39
|
-
puts "pid #{Process.pid}"
|
40
|
-
puts "listening on port 1234"
|
30
|
+
puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 1234"
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
spin { handle_client(client) }
|
32
|
+
server.accept_loop do |c|
|
33
|
+
spin { handle_client(c) }
|
45
34
|
end
|