polyphony 0.40 → 0.41
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 +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
|
}
|