polyphony 0.45.4 → 0.47.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|