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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +11 -2
  3. data/.gitignore +2 -2
  4. data/.rubocop.yml +30 -0
  5. data/CHANGELOG.md +6 -2
  6. data/Gemfile.lock +9 -6
  7. data/Rakefile +2 -2
  8. data/TODO.md +18 -97
  9. data/docs/_includes/head.html +40 -0
  10. data/docs/_includes/nav.html +5 -5
  11. data/docs/api-reference/fiber.md +2 -2
  12. data/docs/main-concepts/design-principles.md +67 -9
  13. data/docs/main-concepts/extending.md +1 -1
  14. data/examples/core/xx-agent.rb +102 -0
  15. data/examples/core/xx-sleeping.rb +14 -6
  16. data/examples/io/xx-irb.rb +1 -1
  17. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  19. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  20. data/ext/{gyro → polyphony}/fiber.c +15 -19
  21. data/ext/{gyro → polyphony}/libev.c +0 -0
  22. data/ext/{gyro → polyphony}/libev.h +0 -0
  23. data/ext/polyphony/libev_agent.c +503 -0
  24. data/ext/polyphony/libev_queue.c +214 -0
  25. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -25
  26. data/ext/polyphony/polyphony.h +90 -0
  27. data/ext/polyphony/polyphony_ext.c +23 -0
  28. data/ext/{gyro → polyphony}/socket.c +14 -14
  29. data/ext/{gyro → polyphony}/thread.c +32 -115
  30. data/ext/{gyro → polyphony}/tracing.c +1 -1
  31. data/lib/polyphony.rb +16 -12
  32. data/lib/polyphony/adapters/irb.rb +1 -1
  33. data/lib/polyphony/adapters/postgres.rb +6 -5
  34. data/lib/polyphony/adapters/process.rb +5 -5
  35. data/lib/polyphony/adapters/trace.rb +28 -28
  36. data/lib/polyphony/core/channel.rb +3 -3
  37. data/lib/polyphony/core/exceptions.rb +1 -1
  38. data/lib/polyphony/core/global_api.rb +11 -9
  39. data/lib/polyphony/core/resource_pool.rb +3 -3
  40. data/lib/polyphony/core/sync.rb +2 -2
  41. data/lib/polyphony/core/thread_pool.rb +6 -6
  42. data/lib/polyphony/core/throttler.rb +13 -6
  43. data/lib/polyphony/event.rb +27 -0
  44. data/lib/polyphony/extensions/core.rb +20 -11
  45. data/lib/polyphony/extensions/fiber.rb +4 -4
  46. data/lib/polyphony/extensions/io.rb +56 -26
  47. data/lib/polyphony/extensions/openssl.rb +4 -8
  48. data/lib/polyphony/extensions/socket.rb +27 -9
  49. data/lib/polyphony/extensions/thread.rb +16 -9
  50. data/lib/polyphony/net.rb +9 -9
  51. data/lib/polyphony/version.rb +1 -1
  52. data/polyphony.gemspec +2 -2
  53. data/test/helper.rb +12 -1
  54. data/test/test_agent.rb +77 -0
  55. data/test/{test_async.rb → test_event.rb} +13 -7
  56. data/test/test_ext.rb +25 -4
  57. data/test/test_fiber.rb +19 -10
  58. data/test/test_global_api.rb +4 -4
  59. data/test/test_io.rb +46 -24
  60. data/test/test_queue.rb +74 -0
  61. data/test/test_signal.rb +3 -40
  62. data/test/test_socket.rb +33 -0
  63. data/test/test_thread.rb +37 -16
  64. data/test/test_trace.rb +6 -5
  65. metadata +24 -24
  66. data/ext/gyro/async.c +0 -132
  67. data/ext/gyro/child.c +0 -108
  68. data/ext/gyro/gyro.h +0 -158
  69. data/ext/gyro/gyro_ext.c +0 -33
  70. data/ext/gyro/io.c +0 -457
  71. data/ext/gyro/queue.c +0 -146
  72. data/ext/gyro/selector.c +0 -205
  73. data/ext/gyro/signal.c +0 -99
  74. data/ext/gyro/timer.c +0 -115
  75. data/test/test_timer.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fac2ba159f62bc767c1c022a9ac84e0d682e9dc1cbb10f53e92ba53da376745
4
- data.tar.gz: 8d43be97359735d5b7ac6b9867716c06c420385bb41f14916edd0a2d2bbca454
3
+ metadata.gz: ebbaa936b265f2e46ff30cab71b7c85e5ebd31396f7b8ecb8fc01156f6e35f79
4
+ data.tar.gz: 677c90c266a7d677f124f964775d849a350f925c0441f9aa6cba3a4745ff1e5d
5
5
  SHA512:
6
- metadata.gz: 0350c4dbee7ad72ba68dfff4fc431870481f43a763d9e265e05ade0ce9545456adef2a79c5efd98ce7fc28b2314ab5558a9e69f9e9ef92759e5ae320c78435be
7
- data.tar.gz: 7c7774de233962a1b06bac02623a6127f9b716e36852bc6e5849227db80fc0e910549756c83441f6d40dd75a22c26253fcfeef7e50d62482c1cc7603f6c4b2f3
6
+ metadata.gz: 732f1eaa117ec2483661451b98a43192ec3e757bde24b4f0eb115fee057efd55fa306af2ed484d3fa943b09e2913a76b9b34a4a8f7eed3a64ea06c27c44b3c17
7
+ data.tar.gz: b9a0595dbf7f338c0b1d67f121090261f0a6efb8c1cb342717a5b94b54a346e1eeadee9b62fd916c4cc3ad400d5466119b3a806f4f611b863682eef10b6b2cfe
@@ -4,12 +4,21 @@ on: [push]
4
4
 
5
5
  jobs:
6
6
  build:
7
- runs-on: ubuntu-latest
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: 2.6.5
21
+ ruby-version: ${{matrix.ruby}}
13
22
  - name: Install dependencies
14
23
  run: |
15
24
  gem install bundler
data/.gitignore CHANGED
@@ -52,8 +52,8 @@ build-iPhoneSimulator/
52
52
  test.rb
53
53
  .vscode
54
54
 
55
- lib/gyro_ext.bundle
56
- lib/gyro_ext.so
55
+ lib/*.bundle
56
+ lib/*.so
57
57
 
58
58
  _site
59
59
  .sass-cache
@@ -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
@@ -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#auto_async` in thread pool, thread extension
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#auto_async` mainly for use in places like `Gyro::Queue#shift`
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`)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.40)
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.80.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, < 1.7)
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.80.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("gyro_ext") do |ext|
8
- ext.ext_dir = "ext/gyro"
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
- - Would it be possible to spin up a fiber on another thread?
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
- For this we need:
3
+ -
8
4
 
9
- - A way to communicate to a thread that it needs to spin up a fiber, the
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
- - Check why first call to `#sleep` returns too early in tests. Check the
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.42 Testing && Docs
129
+ ## 0.45 Testing && Docs
168
130
 
169
131
  - Pull out redis/postgres code, put into new `polyphony-xxx` gems
170
132
 
171
- ## 0.43 Integration
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.45 Rails
143
+ ## 0.47 Rails
184
144
 
185
145
  - Rails?
186
146
 
187
- ## 0.46 DNS
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
- ### Work on API
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
- ```ruby
263
- cancel_after(3, with_error: MyErrorClass) do
264
- do_my_thing
265
- end
266
-
267
- # or a RuntimeError with message
268
- cancel_after(3, with_error: 'Cancelling due to timeout') do
269
- do_my_thing
270
- end
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>
@@ -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 | absolute_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 | absolute_url }}" class="navigation-list-link{% if page.url == node.url %} active{% endif %}">{{ node.title }}</a>
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 | absolute_url -%}
29
+ {%- assign second_level_url = child.url | relative_url -%}
30
30
  {%- endif -%}
31
- <a href="{{ child.url | absolute_url }}" class="navigation-list-link{% if page.url == child.url %} active{% endif %}">{{ child.title }}</a>
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 | absolute_url }}" class="navigation-list-link{% if page.url == grand_child.url %} active{% endif %}">{{ grand_child.title }}</a>
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>
@@ -71,7 +71,7 @@ f << 2
71
71
  result = receive #=> 20
72
72
  ```
73
73
 
74
- ### #auto_async → async
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.auto_async
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 Principles
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 Principles
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 other
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
  }