polyphony 0.40 → 0.41
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 +11 -2
- data/.gitignore +2 -2
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +6 -2
- data/Gemfile.lock +9 -6
- data/Rakefile +2 -2
- data/TODO.md +18 -97
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/nav.html +5 -5
- data/docs/api-reference/fiber.md +2 -2
- data/docs/main-concepts/design-principles.md +67 -9
- data/docs/main-concepts/extending.md +1 -1
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-sleeping.rb +14 -6
- data/examples/io/xx-irb.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
- data/ext/{gyro → polyphony}/extconf.rb +2 -2
- data/ext/{gyro → polyphony}/fiber.c +15 -19
- data/ext/{gyro → polyphony}/libev.c +0 -0
- data/ext/{gyro → polyphony}/libev.h +0 -0
- data/ext/polyphony/libev_agent.c +503 -0
- data/ext/polyphony/libev_queue.c +214 -0
- data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -25
- data/ext/polyphony/polyphony.h +90 -0
- data/ext/polyphony/polyphony_ext.c +23 -0
- data/ext/{gyro → polyphony}/socket.c +14 -14
- data/ext/{gyro → polyphony}/thread.c +32 -115
- data/ext/{gyro → polyphony}/tracing.c +1 -1
- data/lib/polyphony.rb +16 -12
- data/lib/polyphony/adapters/irb.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +6 -5
- data/lib/polyphony/adapters/process.rb +5 -5
- data/lib/polyphony/adapters/trace.rb +28 -28
- data/lib/polyphony/core/channel.rb +3 -3
- data/lib/polyphony/core/exceptions.rb +1 -1
- data/lib/polyphony/core/global_api.rb +11 -9
- data/lib/polyphony/core/resource_pool.rb +3 -3
- data/lib/polyphony/core/sync.rb +2 -2
- data/lib/polyphony/core/thread_pool.rb +6 -6
- data/lib/polyphony/core/throttler.rb +13 -6
- data/lib/polyphony/event.rb +27 -0
- data/lib/polyphony/extensions/core.rb +20 -11
- data/lib/polyphony/extensions/fiber.rb +4 -4
- data/lib/polyphony/extensions/io.rb +56 -26
- data/lib/polyphony/extensions/openssl.rb +4 -8
- data/lib/polyphony/extensions/socket.rb +27 -9
- data/lib/polyphony/extensions/thread.rb +16 -9
- data/lib/polyphony/net.rb +9 -9
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +2 -2
- data/test/helper.rb +12 -1
- data/test/test_agent.rb +77 -0
- data/test/{test_async.rb → test_event.rb} +13 -7
- data/test/test_ext.rb +25 -4
- data/test/test_fiber.rb +19 -10
- data/test/test_global_api.rb +4 -4
- data/test/test_io.rb +46 -24
- data/test/test_queue.rb +74 -0
- data/test/test_signal.rb +3 -40
- data/test/test_socket.rb +33 -0
- data/test/test_thread.rb +37 -16
- data/test/test_trace.rb +6 -5
- metadata +24 -24
- data/ext/gyro/async.c +0 -132
- data/ext/gyro/child.c +0 -108
- data/ext/gyro/gyro.h +0 -158
- data/ext/gyro/gyro_ext.c +0 -33
- data/ext/gyro/io.c +0 -457
- data/ext/gyro/queue.c +0 -146
- data/ext/gyro/selector.c +0 -205
- data/ext/gyro/signal.c +0 -99
- data/ext/gyro/timer.c +0 -115
- data/test/test_timer.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebbaa936b265f2e46ff30cab71b7c85e5ebd31396f7b8ecb8fc01156f6e35f79
|
4
|
+
data.tar.gz: 677c90c266a7d677f124f964775d849a350f925c0441f9aa6cba3a4745ff1e5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 732f1eaa117ec2483661451b98a43192ec3e757bde24b4f0eb115fee057efd55fa306af2ed484d3fa943b09e2913a76b9b34a4a8f7eed3a64ea06c27c44b3c17
|
7
|
+
data.tar.gz: b9a0595dbf7f338c0b1d67f121090261f0a6efb8c1cb342717a5b94b54a346e1eeadee9b62fd916c4cc3ad400d5466119b3a806f4f611b863682eef10b6b2cfe
|
data/.github/workflows/test.yml
CHANGED
@@ -4,12 +4,21 @@ on: [push]
|
|
4
4
|
|
5
5
|
jobs:
|
6
6
|
build:
|
7
|
-
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
os: [ubuntu-latest]
|
11
|
+
ruby: [2.6, 2.7]
|
12
|
+
|
13
|
+
name: >-
|
14
|
+
${{matrix.os}}, ${{matrix.ruby}}
|
15
|
+
|
16
|
+
runs-on: ${{matrix.os}}
|
8
17
|
steps:
|
9
18
|
- uses: actions/checkout@v1
|
10
19
|
- uses: actions/setup-ruby@v1
|
11
20
|
with:
|
12
|
-
ruby-version:
|
21
|
+
ruby-version: ${{matrix.ruby}}
|
13
22
|
- name: Install dependencies
|
14
23
|
run: |
|
15
24
|
gem install bundler
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -143,3 +143,33 @@ Style/HashTransformKeys:
|
|
143
143
|
|
144
144
|
Style/HashTransformValues:
|
145
145
|
Enabled: true
|
146
|
+
|
147
|
+
Layout/EmptyLinesAroundAttributeAccessor:
|
148
|
+
Enabled: true
|
149
|
+
|
150
|
+
Layout/SpaceAroundMethodCallOperator:
|
151
|
+
Enabled: true
|
152
|
+
|
153
|
+
Lint/DeprecatedOpenSSLConstant:
|
154
|
+
Enabled: true
|
155
|
+
|
156
|
+
Lint/MixedRegexpCaptureTypes:
|
157
|
+
Enabled: true
|
158
|
+
|
159
|
+
Lint/RaiseException:
|
160
|
+
Enabled: true
|
161
|
+
|
162
|
+
Lint/StructNewOverride:
|
163
|
+
Enabled: true
|
164
|
+
|
165
|
+
Style/ExponentialNotation:
|
166
|
+
Enabled: true
|
167
|
+
|
168
|
+
Style/RedundantRegexpCharacterClass:
|
169
|
+
Enabled: true
|
170
|
+
|
171
|
+
Style/RedundantRegexpEscape:
|
172
|
+
Enabled: true
|
173
|
+
|
174
|
+
Style/SlicingWithRange:
|
175
|
+
Enabled: true
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 0.41 2020-06-27
|
2
|
+
|
3
|
+
* Introduce System Agent design, remove all `Gyro` classes
|
4
|
+
|
1
5
|
## 0.40 2020-05-04
|
2
6
|
|
3
7
|
* More improvements to stability after fork
|
@@ -21,13 +25,13 @@
|
|
21
25
|
|
22
26
|
* Rename `Fiber#cancel!` to `Fiber#cancel`
|
23
27
|
* Rename `Gyro::Async#signal!` to `Gyro::Async#signal`
|
24
|
-
* Use `Fiber#
|
28
|
+
* Use `Fiber#auto_watcher` in thread pool, thread extension
|
25
29
|
* Implement `Fiber#auto_io` for reusing IO watcher instances
|
26
30
|
* Refactor C code
|
27
31
|
|
28
32
|
## 0.34 2020-03-25
|
29
33
|
|
30
|
-
* Add `Fiber#
|
34
|
+
* Add `Fiber#auto_watcher` mainly for use in places like `Gyro::Queue#shift`
|
31
35
|
* Refactor C extension
|
32
36
|
* Improved GC'ing for watchers
|
33
37
|
* Implement process supervisor (`Polyphony::ProcessSupervisor`)
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
polyphony (0.
|
4
|
+
polyphony (0.41)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -27,7 +27,6 @@ GEM
|
|
27
27
|
multi_xml (>= 0.5.2)
|
28
28
|
i18n (0.9.5)
|
29
29
|
concurrent-ruby (~> 1.0)
|
30
|
-
jaro_winkler (1.5.4)
|
31
30
|
jekyll (3.8.6)
|
32
31
|
addressable (~> 2.4)
|
33
32
|
colorator (~> 1.0)
|
@@ -88,16 +87,20 @@ GEM
|
|
88
87
|
rb-inotify (0.10.1)
|
89
88
|
ffi (~> 1.0)
|
90
89
|
redis (4.1.0)
|
90
|
+
regexp_parser (1.7.1)
|
91
91
|
rexml (3.2.4)
|
92
92
|
rouge (3.15.0)
|
93
|
-
rubocop (0.
|
94
|
-
jaro_winkler (~> 1.5.1)
|
93
|
+
rubocop (0.85.1)
|
95
94
|
parallel (~> 1.10)
|
96
95
|
parser (>= 2.7.0.1)
|
97
96
|
rainbow (>= 2.2.2, < 4.0)
|
97
|
+
regexp_parser (>= 1.7)
|
98
98
|
rexml
|
99
|
+
rubocop-ast (>= 0.0.3)
|
99
100
|
ruby-progressbar (~> 1.7)
|
100
|
-
unicode-display_width (>= 1.4.0, <
|
101
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
102
|
+
rubocop-ast (0.0.3)
|
103
|
+
parser (>= 2.7.0.1)
|
101
104
|
ruby-progressbar (1.10.1)
|
102
105
|
rubyzip (2.0.0)
|
103
106
|
safe_yaml (1.0.5)
|
@@ -131,7 +134,7 @@ DEPENDENCIES
|
|
131
134
|
polyphony!
|
132
135
|
rake-compiler (= 1.0.5)
|
133
136
|
redis (= 4.1.0)
|
134
|
-
rubocop (= 0.
|
137
|
+
rubocop (= 0.85.1)
|
135
138
|
simplecov (= 0.17.1)
|
136
139
|
|
137
140
|
BUNDLED WITH
|
data/Rakefile
CHANGED
@@ -4,8 +4,8 @@ require "bundler/gem_tasks"
|
|
4
4
|
require "rake/clean"
|
5
5
|
|
6
6
|
require "rake/extensiontask"
|
7
|
-
Rake::ExtensionTask.new("
|
8
|
-
ext.ext_dir = "ext/
|
7
|
+
Rake::ExtensionTask.new("polyphony_ext") do |ext|
|
8
|
+
ext.ext_dir = "ext/polyphony"
|
9
9
|
end
|
10
10
|
|
11
11
|
task :recompile => [:clean, :compile]
|
data/TODO.md
CHANGED
@@ -1,42 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
The use case is being able to supervise fibers that run on separate threads.
|
4
|
-
This might be useful for distributing jobs (such as handling HTTP connections)
|
5
|
-
over multiple threads.
|
1
|
+
## 0.42 Update docs
|
6
2
|
|
7
|
-
|
3
|
+
-
|
8
4
|
|
9
|
-
|
10
|
-
simplest solution is to start a fiber accepting spin requests for each
|
11
|
-
thread (in `Thread#initialize`).
|
12
|
-
- An API:
|
13
|
-
|
14
|
-
```ruby
|
15
|
-
spin(on_thread: thread) { do_something_important }
|
16
|
-
```
|
17
|
-
|
18
|
-
An alternative is to turn the main fiber of spawned threads into a child of
|
19
|
-
the spawning fiber. But since a lot of people might start threads without any
|
20
|
-
regard to fibers, it might be better to implement this in a new API. An
|
21
|
-
example of the top of my head for threads that shouldn't be children of the
|
22
|
-
spawning fiber is our own test helper, which kills all child fibers after each
|
23
|
-
test. MiniTest has some threads it spawns for running tests in parallel, and
|
24
|
-
we don't want to stop them after each test!
|
25
|
-
|
26
|
-
So, a good solution would be:
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
t = Thread.new { do_stuff }
|
30
|
-
t.parent_fiber = Fiber.current
|
31
|
-
# or otherwise:
|
32
|
-
Fiber.current.add_child_fiber(t.main_fiber)
|
33
|
-
```
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
## 0.40 Some more API work, more docs
|
5
|
+
## 0.43 Some more API work, more docs
|
40
6
|
|
41
7
|
- Debugging
|
42
8
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
@@ -148,13 +114,9 @@
|
|
148
114
|
- links to the interesting stuff
|
149
115
|
- benchmarks
|
150
116
|
- explain difference between `sleep` and `suspend`
|
151
|
-
- add explanation about async vs sync, blocking vs non-blocking
|
152
117
|
- discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
|
153
118
|
|
154
|
-
|
155
|
-
sleep behaviour in a spawned thread.
|
156
|
-
|
157
|
-
## 0.41 Sinatra / Sidekiq
|
119
|
+
## 0.44 Sinatra / Sidekiq
|
158
120
|
|
159
121
|
- sintra app with database access (postgresql)
|
160
122
|
|
@@ -164,13 +126,11 @@
|
|
164
126
|
- test performance
|
165
127
|
- proceed from there
|
166
128
|
|
167
|
-
## 0.
|
129
|
+
## 0.45 Testing && Docs
|
168
130
|
|
169
131
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
170
132
|
|
171
|
-
## 0.
|
172
|
-
|
173
|
-
## 0.44 Real IO#gets and IO#read
|
133
|
+
## 0.46 Real IO#gets and IO#read
|
174
134
|
|
175
135
|
- More tests
|
176
136
|
- Implement some basic stuff missing:
|
@@ -180,11 +140,11 @@
|
|
180
140
|
- `IO.foreach`
|
181
141
|
- `Process.waitpid`
|
182
142
|
|
183
|
-
## 0.
|
143
|
+
## 0.47 Rails
|
184
144
|
|
185
145
|
- Rails?
|
186
146
|
|
187
|
-
## 0.
|
147
|
+
## 0.48 DNS
|
188
148
|
|
189
149
|
### DNS client
|
190
150
|
|
@@ -217,56 +177,17 @@ Prior art:
|
|
217
177
|
|
218
178
|
- https://github.com/socketry/async-dns
|
219
179
|
|
220
|
-
|
221
|
-
|
222
|
-
- Introduce mailbox limiting:
|
223
|
-
- add API for limiting mailbox size:
|
224
|
-
|
225
|
-
```ruby
|
226
|
-
Fiber.current.mailbox_limit = 1000
|
227
|
-
```
|
228
|
-
|
229
|
-
- Add the limit for `Gyro::Queue`
|
230
|
-
|
231
|
-
```ruby
|
232
|
-
Gyro::Queue.new(1000)
|
233
|
-
```
|
234
|
-
|
235
|
-
- Pushing to a limited queue will block if limit is reached
|
236
|
-
|
237
|
-
- Introduce selective receive:
|
238
|
-
|
239
|
-
```ruby
|
240
|
-
# returns (or waits for) the first message for which the block returns true
|
241
|
-
(_, item) = receive { |msg| msg.first == ref }
|
242
|
-
```
|
243
|
-
|
244
|
-
Possible implementation:
|
245
|
-
|
246
|
-
```ruby
|
247
|
-
def receive
|
248
|
-
return @mailbox.shift unless block_given?
|
249
|
-
|
250
|
-
loop
|
251
|
-
msg = @mailbox.shift
|
252
|
-
return msg if yield(msg)
|
253
|
-
|
254
|
-
# message didn't match condition, put it back in queue
|
255
|
-
@mailbox.push msg
|
256
|
-
end
|
257
|
-
end
|
258
|
-
```
|
180
|
+
## Work on API
|
259
181
|
|
260
182
|
- Add option for setting the exception raised on cancelling using `#cancel_after`:
|
261
183
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
```
|
184
|
+
```ruby
|
185
|
+
cancel_after(3, with_error: MyErrorClass) do
|
186
|
+
do_my_thing
|
187
|
+
end
|
188
|
+
# or a RuntimeError with message
|
189
|
+
cancel_after(3, with_error: 'Cancelled due to timeout') do
|
190
|
+
do_my_thing
|
191
|
+
end
|
192
|
+
```
|
272
193
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<head>
|
2
|
+
<meta charset="UTF-8">
|
3
|
+
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
4
|
+
|
5
|
+
{% if site.plugins.jekyll-seo == nil %}
|
6
|
+
<title>{{ page.title }} - {{ site.title }}</title>
|
7
|
+
|
8
|
+
{% if page.description %}
|
9
|
+
<meta name="Description" content="{{ page.description }}">
|
10
|
+
{% endif %}
|
11
|
+
{% endif %}
|
12
|
+
|
13
|
+
<link rel="shortcut icon" href="{{ '/favicon.ico' | relative_url }}" type="image/x-icon">
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs.css' | relative_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 }}");
|
25
|
+
</script>
|
26
|
+
|
27
|
+
{% endif %}
|
28
|
+
|
29
|
+
{% if site.search_enabled != false %}
|
30
|
+
<script type="text/javascript" src="{{ '/assets/js/vendor/lunr.min.js' | relative_url }}"></script>
|
31
|
+
{% endif %}
|
32
|
+
<script type="text/javascript" src="{{ '/assets/js/just-the-docs.js' | relative_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>
|
data/docs/_includes/nav.html
CHANGED
@@ -9,12 +9,12 @@
|
|
9
9
|
{% if node.section %}section-title{% endif %}
|
10
10
|
">
|
11
11
|
{%- if page.parent == node.title or page.grand_parent == node.title -%}
|
12
|
-
{%- assign first_level_url = node.section_link | node.url |
|
12
|
+
{%- assign first_level_url = node.section_link | node.url | relative_url -%}
|
13
13
|
{%- endif -%}
|
14
14
|
{%- if node.section -%}
|
15
15
|
<span class="section-title">{{ node.title }}</span>
|
16
16
|
{%- else -%}
|
17
|
-
<a href="{{ node.url |
|
17
|
+
<a href="{{ node.url | relative_url }}" class="navigation-list-link{% if page.url == node.url %} active{% endif %}">{{ node.title }}</a>
|
18
18
|
{%- endif -%}
|
19
19
|
{%- if node.has_children -%}
|
20
20
|
{%- if node.alphabetical_order -%}
|
@@ -26,15 +26,15 @@
|
|
26
26
|
{%- for child in children_list -%}
|
27
27
|
<li class="navigation-list-item {% if page.url == child.url or page.parent == child.title %} active{% endif %}">
|
28
28
|
{%- if page.url == child.url or page.parent == child.title -%}
|
29
|
-
{%- assign second_level_url = child.url |
|
29
|
+
{%- assign second_level_url = child.url | relative_url -%}
|
30
30
|
{%- endif -%}
|
31
|
-
<a href="{{ child.url |
|
31
|
+
<a href="{{ child.url | relative_url }}" class="navigation-list-link{% if page.url == child.url %} active{% endif %}">{{ child.title }}</a>
|
32
32
|
{%- if child.has_children -%}
|
33
33
|
{%- assign grand_children_list = site.html_pages | where: "parent", child.title | sort:"nav_order" -%}
|
34
34
|
<ul class="navigation-list-child-list">
|
35
35
|
{%- for grand_child in grand_children_list -%}
|
36
36
|
<li class="navigation-list-item {% if page.url == grand_child.url %} active{% endif %}">
|
37
|
-
<a href="{{ grand_child.url |
|
37
|
+
<a href="{{ grand_child.url | relative_url }}" class="navigation-list-link{% if page.url == grand_child.url %} active{% endif %}">{{ grand_child.title }}</a>
|
38
38
|
</li>
|
39
39
|
{%- endfor -%}
|
40
40
|
</ul>
|
data/docs/api-reference/fiber.md
CHANGED
@@ -71,7 +71,7 @@ f << 2
|
|
71
71
|
result = receive #=> 20
|
72
72
|
```
|
73
73
|
|
74
|
-
### #
|
74
|
+
### #auto_watcher → async
|
75
75
|
|
76
76
|
Returns a reusable `Gyro::Async` watcher instance associated with the fiber.
|
77
77
|
This method provides a way to minimize watcher allocation. Instead of allocating
|
@@ -84,7 +84,7 @@ def work(async)
|
|
84
84
|
async.signal
|
85
85
|
end
|
86
86
|
|
87
|
-
async = Fiber.current.
|
87
|
+
async = Fiber.current.auto_watcher
|
88
88
|
spin { work(async) }
|
89
89
|
async.await
|
90
90
|
```
|
@@ -1,18 +1,76 @@
|
|
1
1
|
---
|
2
2
|
layout: page
|
3
|
-
title: Design
|
3
|
+
title: The Design of Polyphony
|
4
4
|
nav_order: 5
|
5
5
|
parent: Main Concepts
|
6
6
|
permalink: /main-concepts/design-principles/
|
7
7
|
prev_title: Extending Polyphony
|
8
8
|
---
|
9
|
-
# Design
|
9
|
+
# The Design of Polyphony
|
10
|
+
|
11
|
+
Polyphony is a new gem that aims to enable developing high-performance
|
12
|
+
concurrent applications in Ruby using a fluent, compact syntax and API.
|
13
|
+
Polyphony enables fine-grained concurrency - the splitting up of operations into
|
14
|
+
a large number of concurrent tasks, each concerned with small part of the whole
|
15
|
+
and advancing at its own pace. Polyphony aims to solve some of the problems
|
16
|
+
associated with concurrent Ruby programs using a novel design that sets it apart
|
17
|
+
from other approaches currently being used in Ruby.
|
18
|
+
|
19
|
+
## Origins
|
20
|
+
|
21
|
+
The Ruby core language (at least in its MRI implementation) currently provides
|
22
|
+
two main constructs for performing concurrent work: threads and fibers. While
|
23
|
+
Ruby threads are basically wrappers for OS threads, fibers are essentially
|
24
|
+
continuations, allowing pausing and resuming distinct computations. Fibers have
|
25
|
+
been traditionally used mostly for implementing enumerators and generators.
|
26
|
+
|
27
|
+
In addition to the core Ruby concurrency primitives, some Ruby gems have been
|
28
|
+
offering an alternative solution to writing concurrent Ruby apps, most notably
|
29
|
+
[EventMachine](https://github.com/eventmachine/eventmachine/), which implements
|
30
|
+
an event reactor and offers an asynchronous callback-based API for writing
|
31
|
+
concurrent code.
|
32
|
+
|
33
|
+
In the last couple of years, however, fibers have been receiving more attention
|
34
|
+
as a possible constructs for writing concurrent programs. In particular, the
|
35
|
+
[Async](https://github.com/socketry/async) framework, created by [Samuel
|
36
|
+
Williams](https://github.com/ioquatix), offering a comprehensive set of
|
37
|
+
libraries, employs fibers in conjunction with an event reactor provided by the
|
38
|
+
[nio4r](https://github.com/socketry/nio4r) gem, which wraps the C
|
39
|
+
library [libev](http://software.schmorp.de/pkg/libev.html).
|
40
|
+
|
41
|
+
In addition, recently some effort was undertaken to provide a way to
|
42
|
+
[automatically switch between fibers](https://bugs.ruby-lang.org/issues/13618)
|
43
|
+
whenever a blocking operation is performed, or to [integrate a fiber
|
44
|
+
scheduler](https://bugs.ruby-lang.org/issues/16786) into the core Ruby code.
|
45
|
+
|
46
|
+
Nevertheless, while work is being done to harness fibers for providing a better
|
47
|
+
way to do concurrency in Ruby, fibers remain a mistery for most Ruby
|
48
|
+
programmers, a perplexing unfamiliar corner right at the heart of Ruby.
|
49
|
+
|
50
|
+
## Design Principles
|
51
|
+
|
52
|
+
Polyphony started as an experiment, but over about two years of slow, jerky
|
53
|
+
evolution turned into something I'm really excited to share with the Ruby
|
54
|
+
community. Polyphony's design is both similar and different than the projects
|
55
|
+
mentioned above.
|
56
|
+
|
57
|
+
Polyphony today as nothing like the way it began. A careful examination of the
|
58
|
+
[CHANGELOG](https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md)
|
59
|
+
would show how Polyphony explored not only different event reactor designs, but
|
60
|
+
also different API designs incorporating various concurrent paradigms such as
|
61
|
+
promises, async/await, fibers, and finally structured concurrency.
|
62
|
+
|
63
|
+
While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
|
64
|
+
blocking operations into non-blocking ones, it completely embraces fibers and in
|
65
|
+
fact does not provide any callback-based APIs. Furthermore, Polyphony provides
|
66
|
+
fullblown fiber-aware implementations of blocking operations, such as
|
67
|
+
`read/write`, `sleep` or `waitpid`, instead of just event watching primitives.
|
68
|
+
|
69
|
+
Throughout the development process, it was my intention to create a programming
|
70
|
+
interface that would make highly-concurrent
|
71
|
+
|
72
|
+
|
10
73
|
|
11
|
-
Polyphony was created in order to enable developing high-performance concurrent
|
12
|
-
applications in Ruby using a fluent, compact syntax and API. Polyphony enables
|
13
|
-
fine-grained concurrency - the splitting up of operations into a large number of
|
14
|
-
concurrent tasks, each concerned with small part of the whole and advancing at
|
15
|
-
its own pace.
|
16
74
|
|
17
75
|
|
18
76
|
|
@@ -46,8 +104,8 @@ library. Polyphony's design is based on the following principles:
|
|
46
104
|
async callback-style APIs.
|
47
105
|
|
48
106
|
```ruby
|
49
|
-
# in Polyphony, I/O ops block the current fiber, but implicitly yield to
|
50
|
-
# concurrent fibers:
|
107
|
+
# in Polyphony, I/O ops might block the current fiber, but implicitly yield to
|
108
|
+
# other concurrent fibers:
|
51
109
|
clients.each { |client|
|
52
110
|
spin { client.puts 'Elvis has left the chatroom' }
|
53
111
|
}
|