polyphony 0.43.5 → 0.43.11
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/CHANGELOG.md +45 -0
- data/Gemfile.lock +1 -1
- data/README.md +21 -4
- data/TODO.md +0 -7
- data/bin/stress.rb +28 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_user-guide/web-server.md +11 -11
- data/docs/getting-started/overview.md +2 -2
- data/docs/index.md +3 -1
- data/docs/polyphony-logo.png +0 -0
- data/examples/core/xx-channels.rb +4 -2
- data/examples/core/xx-using-a-mutex.rb +2 -1
- data/examples/io/xx-happy-eyeballs.rb +21 -22
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/polyphony/agent.h +41 -0
- data/ext/polyphony/event.c +86 -0
- data/ext/polyphony/fiber.c +0 -5
- data/ext/polyphony/libev_agent.c +277 -135
- data/ext/polyphony/polyphony.c +2 -2
- data/ext/polyphony/polyphony.h +14 -21
- data/ext/polyphony/polyphony_ext.c +4 -2
- data/ext/polyphony/queue.c +208 -0
- data/ext/polyphony/ring_buffer.c +0 -24
- data/ext/polyphony/thread.c +42 -31
- data/lib/polyphony.rb +6 -7
- data/lib/polyphony/core/channel.rb +3 -34
- data/lib/polyphony/core/resource_pool.rb +13 -75
- data/lib/polyphony/core/sync.rb +12 -9
- data/lib/polyphony/extensions/fiber.rb +8 -8
- data/lib/polyphony/extensions/openssl.rb +8 -0
- data/lib/polyphony/extensions/socket.rb +11 -9
- data/lib/polyphony/extensions/thread.rb +1 -1
- data/lib/polyphony/net.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +2 -2
- data/test/test_agent.rb +2 -2
- data/test/test_event.rb +12 -0
- data/test/test_fiber.rb +1 -1
- data/test/test_io.rb +14 -0
- data/test/test_queue.rb +33 -0
- data/test/test_resource_pool.rb +24 -58
- data/test/test_trace.rb +18 -17
- metadata +12 -5
- data/ext/polyphony/libev_queue.c +0 -288
- data/lib/polyphony/event.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d1eb9e2b1eef7f180107b3952fc6974e9c816268ebd8d2368ddfaac276d2086
|
4
|
+
data.tar.gz: dafb382dc7606b92b97fb98125b57abc72e1b1d6b07bb877abff608a41ee51a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f91b787b3eec2c19f6a02fd8a77469c8ae5d7a9302799c3e2a5957376b9f4b0135d2cd958ec7dcf326701e8ba81d85a28201a140ce5806ccc057cedafe907d5
|
7
|
+
data.tar.gz: df865277e2f37c0d7fde0ffd38f52fc466bf28033fb968bd012af7455df9e7805989152467e1e8e84719194f173c99c6290496987ad8a2b48fe26076e62c85bd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,48 @@
|
|
1
|
+
## 0.43.11 2020-07-24
|
2
|
+
|
3
|
+
* Dump uncaught exception info for forked process (#36)
|
4
|
+
* Add additional socket config options (#37)
|
5
|
+
- :reuse_port (`SO_REUSEPORT`)
|
6
|
+
- :backlog (listen backlog, default `SOMAXCONN`)
|
7
|
+
* Fix possible race condition in Queue#shift (#34)
|
8
|
+
|
9
|
+
## 0.43.10 2020-07-23
|
10
|
+
|
11
|
+
* Fix race condition when terminating fibers (#33)
|
12
|
+
* Fix lock release in `Mutex` (#32)
|
13
|
+
* Virtualize agent interface
|
14
|
+
* Implement `LibevAgent_connect`
|
15
|
+
|
16
|
+
## 0.43.9 2020-07-22
|
17
|
+
|
18
|
+
* Rewrite `Channel` using `Queue`
|
19
|
+
* Rewrite `Mutex` using `Queue`
|
20
|
+
* Reimplement `Event` in C to prevent cross-thread race condition
|
21
|
+
* Reimplement `ResourcePool` using `Queue`
|
22
|
+
* Implement `Queue#size`
|
23
|
+
|
24
|
+
## 0.43.8 2020-07-21
|
25
|
+
|
26
|
+
* Rename `LibevQueue` to `Queue`
|
27
|
+
* Reimplement Event using `Agent#wait_event`
|
28
|
+
* Improve Queue shift queue performance
|
29
|
+
* Introduce `Agent#wait_event` API for waiting on asynchronous events
|
30
|
+
* Minimize `fcntl` syscalls in IO operations
|
31
|
+
|
32
|
+
## 0.43.7 2020-07-20
|
33
|
+
|
34
|
+
* Fix memory leak in ResourcePool (#31)
|
35
|
+
* Check and adjust file position before reading (#30)
|
36
|
+
* Minor documentation fixes
|
37
|
+
|
38
|
+
## 0.43.6 2020-07-18
|
39
|
+
|
40
|
+
* Allow brute-force interrupting with second Ctrl-C
|
41
|
+
* Fix outgoing SSL connections (#28)
|
42
|
+
* Improve Fiber#await_all_children with many children
|
43
|
+
* Use `writev` for writing multiple strings
|
44
|
+
* Add logo (thanks [Gerald](https://webocube.com/)!)
|
45
|
+
|
1
46
|
## 0.43.5 2020-07-13
|
2
47
|
|
3
48
|
* Fix `#read_nonblock`, `#write_nonblock` for `IO` and `Socket` (#27)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,25 @@
|
|
1
|
-
|
1
|
+
<h1 align="center">
|
2
|
+
<a href="https://digital-fabric.github.io/polyphony/">
|
3
|
+
<img src="docs/polyphony-logo.png" alt="Polyphony">
|
4
|
+
</a>
|
5
|
+
<br>
|
6
|
+
Polyphony
|
7
|
+
<br>
|
8
|
+
</h1>
|
2
9
|
|
3
|
-
|
4
|
-
|
5
|
-
|
10
|
+
<h4 align="center">Fine-Grained Concurrency for Ruby</h4>
|
11
|
+
|
12
|
+
<p align="center">
|
13
|
+
<a href="http://rubygems.org/gems/polyphony">
|
14
|
+
<img src="https://badge.fury.io/rb/polyphony.svg" alt="Ruby gem">
|
15
|
+
</a>
|
16
|
+
<a href="https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests">
|
17
|
+
<img src="https://github.com/digital-fabric/polyphony/workflows/Tests/badge.svg" alt="Tests">
|
18
|
+
</a>
|
19
|
+
<a href="https://github.com/digital-fabric/polyphony/blob/master/LICENSE">
|
20
|
+
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
|
21
|
+
</a>
|
22
|
+
</p>
|
6
23
|
|
7
24
|
[DOCS](https://digital-fabric.github.io/polyphony/) |
|
8
25
|
[EXAMPLES](examples)
|
data/TODO.md
CHANGED
@@ -1,10 +1,3 @@
|
|
1
|
-
## 0.43
|
2
|
-
|
3
|
-
- Reimplement ResourcePool, Channel, Mutex using LibevQueue
|
4
|
-
-- Add `Fiber#schedule_with_priority` method, aliased by `Fiber#wakeup`
|
5
|
-
- Implement agent interface is virtual function table
|
6
|
-
- Implement proxy agent for plugging in a user-provided agent class
|
7
|
-
|
8
1
|
- Debugging
|
9
2
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
10
3
|
test fails
|
data/bin/stress.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
`rake recompile`
|
5
|
+
|
6
|
+
count = ARGV[0] ? ARGV[0].to_i : 100
|
7
|
+
|
8
|
+
TEST_CMD = 'ruby test/run.rb'
|
9
|
+
|
10
|
+
def run_test(count)
|
11
|
+
puts "#{count}: running tests..."
|
12
|
+
system(TEST_CMD)
|
13
|
+
return if $?.exitstatus == 0
|
14
|
+
|
15
|
+
puts "Failure after #{count} tests"
|
16
|
+
exit!
|
17
|
+
end
|
18
|
+
|
19
|
+
trap('INT') { exit! }
|
20
|
+
t0 = Time.now
|
21
|
+
count.times { |i| run_test(i + 1) }
|
22
|
+
elapsed = Time.now - t0
|
23
|
+
puts format(
|
24
|
+
"Successfully ran %d tests in %f seconds (%f per test)",
|
25
|
+
count,
|
26
|
+
elapsed,
|
27
|
+
elapsed / count
|
28
|
+
)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<head>
|
2
|
+
<meta charset="UTF-8">
|
3
|
+
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
4
|
+
|
5
|
+
{% unless site.plugins contains "jekyll-seo-tag" %}
|
6
|
+
<title>{{ page.title }} - {{ site.title }}</title>
|
7
|
+
|
8
|
+
{% if page.description %}
|
9
|
+
<meta name="Description" content="{{ page.description }}">
|
10
|
+
{% endif %}
|
11
|
+
{% endunless %}
|
12
|
+
|
13
|
+
<link rel="shortcut icon" href="{{ 'polyphony-logo.png' | absolute_url }}" type="image/png">
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | absolute_url }}">
|
16
|
+
|
17
|
+
{% if site.ga_tracking != nil %}
|
18
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.ga_tracking }}"></script>
|
19
|
+
<script>
|
20
|
+
window.dataLayer = window.dataLayer || [];
|
21
|
+
function gtag(){dataLayer.push(arguments);}
|
22
|
+
gtag('js', new Date());
|
23
|
+
|
24
|
+
gtag('config', '{{ site.ga_tracking }}'{% unless site.ga_tracking_anonymize_ip == nil %}, { 'anonymize_ip': true }{% endunless %});
|
25
|
+
</script>
|
26
|
+
|
27
|
+
{% endif %}
|
28
|
+
|
29
|
+
{% if site.search_enabled != false %}
|
30
|
+
<script type="text/javascript" src="{{ '/assets/js/vendor/lunr.min.js' | absolute_url }}"></script>
|
31
|
+
{% endif %}
|
32
|
+
<script type="text/javascript" src="{{ '/assets/js/just-the-docs.js' | absolute_url }}"></script>
|
33
|
+
|
34
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
35
|
+
|
36
|
+
{% seo %}
|
37
|
+
|
38
|
+
{% include head_custom.html %}
|
39
|
+
|
40
|
+
</head>
|
@@ -0,0 +1 @@
|
|
1
|
+
<img src="{{ 'polyphony-logo.png' | absolute_url }}" style="height: 1.5em; margin-right: 0.5em">Polyphony
|
@@ -26,9 +26,9 @@ the entire request body.
|
|
26
26
|
## A basic web server
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
require '
|
29
|
+
require 'tipi'
|
30
30
|
|
31
|
-
|
31
|
+
Tipi.serve('0.0.0.0', 1234) do |request|
|
32
32
|
request.respond("Hello world!\n")
|
33
33
|
end
|
34
34
|
```
|
@@ -55,13 +55,13 @@ TLS termination can be handled by passing a `secure_context` option to the
|
|
55
55
|
server:
|
56
56
|
|
57
57
|
```ruby
|
58
|
-
require '
|
58
|
+
require 'tipi'
|
59
59
|
require 'localhost/authority'
|
60
60
|
|
61
61
|
authority = Localhost::Authority.fetch
|
62
62
|
opts = { secure_context: authority.server_context }
|
63
63
|
|
64
|
-
|
64
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |request|
|
65
65
|
request.respond("Hello world!\n")
|
66
66
|
end
|
67
67
|
```
|
@@ -72,8 +72,8 @@ Polyphony's web server makes it really easy to integrate websocket communication
|
|
72
72
|
with normal HTTP processing:
|
73
73
|
|
74
74
|
```ruby
|
75
|
-
require '
|
76
|
-
require '
|
75
|
+
require 'tipi'
|
76
|
+
require 'tipi/websocket'
|
77
77
|
|
78
78
|
ws_handler = Polyphony::Websocket.handler do |ws|
|
79
79
|
while (msg = ws.recv)
|
@@ -85,7 +85,7 @@ opts = {
|
|
85
85
|
upgrade: { websocket: ws_handler }
|
86
86
|
}
|
87
87
|
|
88
|
-
|
88
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |request|
|
89
89
|
request.respond("Hello world!\n")
|
90
90
|
end
|
91
91
|
```
|
@@ -93,7 +93,7 @@ end
|
|
93
93
|
Polyphony also supports general-purpose HTTP upgrades using the same mechanism:
|
94
94
|
|
95
95
|
```ruby
|
96
|
-
require '
|
96
|
+
require 'tipi'
|
97
97
|
|
98
98
|
opts = {
|
99
99
|
upgrade: {
|
@@ -105,7 +105,7 @@ opts = {
|
|
105
105
|
}
|
106
106
|
}
|
107
107
|
|
108
|
-
|
108
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |request|
|
109
109
|
request.respond("Hello world!\n")
|
110
110
|
end
|
111
111
|
```
|
@@ -117,7 +117,7 @@ and enables streaming (using chunked encoding for HTTP/1.1 connections). Here's
|
|
117
117
|
an example of an SSE response:
|
118
118
|
|
119
119
|
```ruby
|
120
|
-
require '
|
120
|
+
require 'tipi'
|
121
121
|
|
122
122
|
def sse_response(request)
|
123
123
|
request.send_headers('Content-Type': 'text/event-stream')
|
@@ -131,6 +131,6 @@ ensure
|
|
131
131
|
request.send_chunk("retry: 0\n\n", done: true)
|
132
132
|
end
|
133
133
|
|
134
|
-
|
134
|
+
Tipi.serve('0.0.0.0', 1234, &method(:sse_response))
|
135
135
|
```
|
136
136
|
|
@@ -113,8 +113,8 @@ active concurrent connections, each advancing at its own pace, consuming only a
|
|
113
113
|
single CPU core.
|
114
114
|
|
115
115
|
Nevertheless, Polyphony fully supports multithreading, with each thread having
|
116
|
-
its own fiber run queue and its own libev event loop.
|
117
|
-
|
116
|
+
its own fiber run queue and its own libev event loop. Polyphony even enables
|
117
|
+
cross-thread communication using [fiber messaging](#message-passing).
|
118
118
|
|
119
119
|
## Fibers vs Callbacks
|
120
120
|
|
data/docs/index.md
CHANGED
@@ -6,6 +6,8 @@ permalink: /
|
|
6
6
|
next_title: Installing Polyphony
|
7
7
|
---
|
8
8
|
|
9
|
+
<p align="center"><img src="{{ 'polyphony-logo.png' | absolute_url }}" /></p>
|
10
|
+
|
9
11
|
# Polyphony
|
10
12
|
{:.text-center .logo-title}
|
11
13
|
|
@@ -21,7 +23,7 @@ for I/O, timers, and other asynchronous events.
|
|
21
23
|
[Overview](getting-started/overview){: .btn .btn-green .text-gamma }
|
22
24
|
[Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
|
23
25
|
[Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
|
24
|
-
{
|
26
|
+
{:.text-center .mt-6 .h-align-center }
|
25
27
|
|
26
28
|
## Focused on Developer Happiness
|
27
29
|
|
data/docs/polyphony-logo.png
CHANGED
Binary file
|
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony'
|
5
|
+
require 'polyphony/core/channel'
|
5
6
|
|
6
7
|
def echo(cin, cout)
|
7
8
|
puts 'start echoer'
|
8
9
|
while (msg = cin.receive)
|
10
|
+
puts "echoer received #{msg}"
|
9
11
|
cout << "you said: #{msg}"
|
10
12
|
end
|
11
13
|
ensure
|
@@ -20,7 +22,7 @@ spin do
|
|
20
22
|
puts 'start receiver'
|
21
23
|
while (msg = chan2.receive)
|
22
24
|
puts msg
|
23
|
-
$main.
|
25
|
+
$main.schedule if msg =~ /world/
|
24
26
|
end
|
25
27
|
ensure
|
26
28
|
puts 'receiver stopped'
|
@@ -42,4 +44,4 @@ $main = spin do
|
|
42
44
|
puts "done #{Time.now - t0}"
|
43
45
|
end
|
44
46
|
|
45
|
-
|
47
|
+
$main.await
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony'
|
5
|
+
require 'polyphony/core/sync'
|
5
6
|
|
6
7
|
def loop_it(number, lock)
|
7
8
|
loop do
|
@@ -13,7 +14,7 @@ def loop_it(number, lock)
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
lock = Polyphony::
|
17
|
+
lock = Polyphony::Mutex.new
|
17
18
|
spin { loop_it(1, lock) }
|
18
19
|
spin { loop_it(2, lock) }
|
19
20
|
spin { loop_it(3, lock) }
|
@@ -1,37 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# idea taken from the example given in trio:
|
4
|
-
# https://www.youtube.com/watch?v=oLkfnc_UMcE
|
5
|
-
|
6
|
-
require 'bundler/setup'
|
7
3
|
require 'polyphony'
|
8
4
|
|
9
|
-
def try_connect(
|
10
|
-
puts "trying #{
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
def try_connect(ip_address, port, supervisor)
|
6
|
+
puts "trying #{ip_address}"
|
7
|
+
sleep rand * 0.2
|
8
|
+
socket = TCPSocket.new(ip_address, port)
|
9
|
+
puts "connected to #{ip_address}"
|
10
|
+
supervisor.schedule [ip_address, socket]
|
14
11
|
rescue IOError, SystemCallError
|
15
12
|
# ignore error
|
16
13
|
end
|
17
14
|
|
18
|
-
def happy_eyeballs(hostname, port, max_wait_time: 0.
|
15
|
+
def happy_eyeballs(hostname, port, max_wait_time: 0.010)
|
19
16
|
targets = Socket.getaddrinfo(hostname, port, :INET, :STREAM)
|
20
17
|
t0 = Time.now
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
if success
|
29
|
-
puts format('success: %s (%.3fs)', success[0], Time.now - t0)
|
30
|
-
else
|
31
|
-
puts "timed out (#{Time.now - t0}s)"
|
18
|
+
fibers = []
|
19
|
+
supervisor = Fiber.current
|
20
|
+
spin do
|
21
|
+
targets.each do |t|
|
22
|
+
spin { try_connect(t[2], t[1], supervisor) }
|
23
|
+
sleep(max_wait_time)
|
32
24
|
end
|
25
|
+
suspend
|
26
|
+
end
|
27
|
+
target, socket = move_on_after(5) { suspend }
|
28
|
+
supervisor.shutdown_all_children
|
29
|
+
if target
|
30
|
+
puts format('success: %s (%.3fs)', target, Time.now - t0)
|
31
|
+
else
|
32
|
+
puts 'timed out'
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
# Let's try it out:
|
37
36
|
happy_eyeballs('debian.org', 'https')
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
i, o = IO.pipe
|
8
|
+
|
9
|
+
w = Zlib::GzipWriter.new(o)
|
10
|
+
|
11
|
+
s = (1..1000).map { (65 + rand(26)).chr }.join
|
12
|
+
puts "full length: #{s.bytesize}"
|
13
|
+
w << s
|
14
|
+
w.close
|
15
|
+
o.close
|
16
|
+
|
17
|
+
|
18
|
+
z = i.read
|
19
|
+
puts "zipped length: #{z.bytesize}"
|
@@ -0,0 +1,47 @@
|
|
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
|
10
|
+
|
11
|
+
def run(num_fibers)
|
12
|
+
count = 0
|
13
|
+
|
14
|
+
GC.disable
|
15
|
+
|
16
|
+
first = nil
|
17
|
+
last = nil
|
18
|
+
supervisor = Fiber.current
|
19
|
+
num_fibers.times do
|
20
|
+
fiber = Fiber.new do
|
21
|
+
loop do
|
22
|
+
count += 1
|
23
|
+
if count == 1_000_000
|
24
|
+
supervisor.transfer
|
25
|
+
else
|
26
|
+
Fiber.current.next.transfer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
first ||= fiber
|
31
|
+
last.next = fiber if last
|
32
|
+
last = fiber
|
33
|
+
end
|
34
|
+
|
35
|
+
last.next = first
|
36
|
+
|
37
|
+
t0 = Time.now
|
38
|
+
first.transfer
|
39
|
+
elapsed = Time.now - t0
|
40
|
+
|
41
|
+
puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
|
42
|
+
GC.start
|
43
|
+
end
|
44
|
+
|
45
|
+
run(100)
|
46
|
+
run(1000)
|
47
|
+
run(10000)
|