polyphony 0.27 → 0.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +12 -1
  5. data/Gemfile.lock +83 -5
  6. data/Rakefile +4 -0
  7. data/TODO.md +11 -20
  8. data/docs/_config.yml +15 -0
  9. data/docs/_includes/nav.html +47 -0
  10. data/docs/_sass/custom/custom.scss +5 -0
  11. data/docs/_sass/overrides.scss +45 -0
  12. data/docs/assets/img/echo-fibers.svg +1 -0
  13. data/docs/assets/img/sleeping-fiber.svg +1 -0
  14. data/docs/faq.md +182 -0
  15. data/docs/getting-started/installing.md +10 -2
  16. data/docs/getting-started/tutorial.md +333 -26
  17. data/docs/getting-started.md +10 -0
  18. data/docs/index.md +91 -0
  19. data/docs/technical-overview/concurrency.md +78 -16
  20. data/docs/technical-overview/design-principles.md +7 -0
  21. data/docs/technical-overview/exception-handling.md +57 -9
  22. data/docs/technical-overview/extending.md +7 -0
  23. data/docs/technical-overview/fiber-scheduling.md +128 -18
  24. data/docs/technical-overview.md +10 -0
  25. data/docs/user-guide/web-server.md +7 -0
  26. data/docs/user-guide.md +10 -0
  27. data/examples/core/xx-deadlock.rb +8 -0
  28. data/examples/core/xx-state-machine.rb +51 -0
  29. data/examples/core/xx-trace.rb +80 -0
  30. data/examples/interfaces/pg_notify.rb +35 -0
  31. data/examples/io/xx-httparty.rb +31 -6
  32. data/examples/io/xx-irb.rb +1 -11
  33. data/examples/io/xx-switch.rb +15 -0
  34. data/ext/gyro/gyro.c +77 -38
  35. data/ext/gyro/gyro.h +15 -5
  36. data/ext/gyro/gyro_ext.c +3 -0
  37. data/ext/gyro/thread.c +47 -32
  38. data/ext/gyro/tracing.c +11 -0
  39. data/lib/polyphony/core/global_api.rb +11 -4
  40. data/lib/polyphony/core/supervisor.rb +1 -0
  41. data/lib/polyphony/core/thread_pool.rb +44 -35
  42. data/lib/polyphony/extensions/fiber.rb +19 -9
  43. data/lib/polyphony/extensions/io.rb +14 -14
  44. data/lib/polyphony/extensions/socket.rb +3 -3
  45. data/lib/polyphony/irb.rb +13 -0
  46. data/lib/polyphony/postgres.rb +15 -0
  47. data/lib/polyphony/trace.rb +98 -0
  48. data/lib/polyphony/version.rb +1 -1
  49. data/lib/polyphony.rb +1 -0
  50. data/polyphony.gemspec +21 -12
  51. data/test/helper.rb +3 -2
  52. data/test/test_fiber.rb +53 -3
  53. data/test/test_global_api.rb +12 -0
  54. data/test/test_gyro.rb +2 -2
  55. data/test/test_supervisor.rb +12 -0
  56. data/test/test_thread.rb +12 -0
  57. data/test/test_thread_pool.rb +75 -0
  58. data/test/test_throttler.rb +6 -0
  59. data/test/test_trace.rb +66 -0
  60. metadata +99 -9
  61. data/docs/README.md +0 -36
  62. data/docs/summary.md +0 -60
  63. data/docs/technical-overview/faq.md +0 -97
@@ -1,9 +1,143 @@
1
- # Tutorial
1
+ ---
2
+ layout: page
3
+ title: A Gentle Introduction to Polyphony
4
+ nav_order: 2
5
+ parent: Getting Started
6
+ permalink: /getting-started/tutorial/
7
+ ---
8
+ # A Gentle Introduction to Polyphony
9
+
10
+ Polyphony is a new Ruby library aimed at making writing concurrent Ruby apps
11
+ easy and fun. In this article, we'll introduce Polyphony's fiber-based
12
+ concurrency model, some of Polyphony's API, and demonstrate how to solve some
13
+ simple situations related to concurrent computing.
14
+
15
+ ## What are Fibers and What are They Good For?
16
+
17
+ Fibers are some of Ruby's most underappreciated hidden gems. Up until now,
18
+ fibers have been used mostly as the underlying mechanism for implementing
19
+ lazy enumerators and asynchronous generators. Fibers encapsulate, in short,
20
+ an execution context that can be paused and resumed at will.
21
+
22
+ Fibers are also at the heart of Polyphony's concurrency model. Polyphony employs
23
+ fibers as a way to run multiple tasks at once, each task advancing at its own
24
+ pace, pausing when waiting for an event to occur, and automatically resuming
25
+ when that event has occurred.
26
+
27
+ Take for example a web app: in order to fulfil an incoming request, multiple
28
+ steps are required: querying the database, fetching cached entries from Redis,
29
+ talking to third-party services such as Twilio or AWS S3. Each step can last
30
+ tens of milliseconds, and blocks the current thread. Such an app is said to be
31
+ I/O-bound, that is, it mostly spends its time waiting for some external
32
+ services.
33
+
34
+ The traditional approach to handling multiple requests concurrently is to employ
35
+ multiple threads or processes, but this approach has numerous disavantages:
36
+
37
+ - Both threads and processes are heavyweight, in both memory consmption and
38
+ the cost associated with context-switching.
39
+ - Threads introduce hard-to-debug race conditions, and do not offer true
40
+ parallelism, owing to Ruby's GVL.
41
+ - Processes are more difficult to coordinate, since they do not share memory.
42
+ - Both threads and processes are limited to a few thousand at best on a single
43
+ machine. Trying to spawn a thread per client essentially limits the scaling
44
+ capacity of your system.
45
+
46
+ Polyphony eschews both threads and processes in favor of fibers as the basic
47
+ unit of concurrency. The idea is that any time a blocking I/O operation occurs,
48
+ the current fiber is paused, and another fiber which has been marked as
49
+ *runnable* is resumed. This way, your Ruby code can keep on handling incoming
50
+ HTTP requests as they come with a scaling capacity that is virtually only
51
+ limited by available memory.
52
+
53
+ ## Switchpoints and the Fiber-Switching Dance
54
+
55
+ In order to make pausing and resuming fibers completely automatic and painfree,
56
+ we need to know when an operation is going to block, and when it can be
57
+ completed without blocking. Operations that might block execution are considered
58
+ *switchpoints*. A switchpoint is a point in time at which control might switch
59
+ from the currently running fiber to another fiber that is in a runnable state.
60
+ Switchpoints may occur in any of the following cases:
61
+
62
+ - On a call to any blocking operation, such as `#sleep`, `Fiber#await`,
63
+ `Thread#join` etc.
64
+ - On fiber termination
65
+ - On a call to `#suspend`
66
+ - On a call to `#snooze`
67
+ - On a call to `Thread#switch_fiber`
68
+
69
+ At any switchpoint, the following takes place:
70
+
71
+ - Check if any fiber is runnable, that is, ready to continue processing.
72
+ - If no fiber is runnable, watch for events (see below) and wait for at least
73
+ one fiber to become runnable.
74
+ - Pause the current fiber and switch to the first runnable fiber, which resumes
75
+ at the point it was last paused.
76
+
77
+ The automatic switching between fibers is complemented by employing
78
+ [libev](http://software.schmorp.de/pkg/libev.html), a multi-platform high
79
+ performance event reactor that allows listening to I/O, timer and other events.
80
+ At every switchpoint where no fibers are runnable, the libev evet loop is run
81
+ until events occur, which in turn cause the relevant fibers to become runnable.
82
+
83
+ Let's examine a simple example:
84
+
85
+ ```ruby
86
+ require 'polyphony'
87
+
88
+ spin do
89
+ puts "Going to sleep..."
90
+ sleep 1
91
+ puts "Woke up"
92
+ end
93
+
94
+ suspend
95
+ puts "We're done"
96
+ ```
97
+
98
+ The above program does nothing exceptional, it just sleeps for 1 second and
99
+ prints a bunch of messages. But it is enough to demonstrate how concurrency
100
+ works in Polyphony. Here's a flow chart of the transfer of control:
101
+
102
+ <p class="img-figure"><img src="../../assets/img/sleeping-fiber.svg"></p>
103
+
104
+ Here's the actual sequence of execution (in pseudo-code)
105
+
106
+ ```ruby
107
+ # (main fiber)
108
+ sleeper = spin { ... } # The main fiber spins up a new fiber marked as runnable
109
+ suspend # The main fiber suspends, waiting for all other work to finish
110
+ Thread.current.switch_fiber # Polyphony looks for other runnable fibers
111
+
112
+ # (sleeper fiber)
113
+ puts "Going to sleep..." # The sleeper fiber starts running
114
+ sleep 1 # The sleeper fiber goes to sleep
115
+ Gyro::Timer.new(1, 0).await # A timer event watcher is setup and yields
116
+ Thread.current.switch_fiber # Polyphony looks for other runnable fibers
117
+ Thread.current.selector.run # With no work left, the event loop is ran
118
+ fiber.schedule # The timer event fires, scheduling the sleeper fiber
119
+ # <= The sleep method returns
120
+ puts "Woke up"
121
+ Thread.current.switch_fiber # With the fiber done, Polyphony looks for work
122
+
123
+ # with no more work, control is returned to the main fiber
124
+ # (main fiber)
125
+ # <=
126
+ # With no more work left, the main fiber is resumed and the suspend call returns
127
+ puts "We're done"
128
+ ```
129
+
130
+ What we have done in fact is we multiplexed two different contexts of execution
131
+ (fibers) onto a single thread, each fiber continuing at its own pace and
132
+ yielding control when waiting for something to happen. This context-switching
133
+ dance, performed automatically by Polyphony behind the scenes, enables building
134
+ highly-concurrent Ruby apps, with minimal impact on performance.
2
135
 
3
136
  ## Building a Simple Echo Server with Polyphony
4
137
 
5
- In order to demonstrate how to use Polyphony, let's write an echo server, which
6
- accepts TCP connections and sends back whatever it receives from the client.
138
+ Let's now turn our attention to something a bit more useful: a concurrent echo
139
+ server. Our server will accept TCP connections and send back whatever it receives
140
+ from the client.
7
141
 
8
142
  We'll start by opening a server socket:
9
143
 
@@ -42,7 +176,7 @@ continue to run until the call to `#handle_client` returns, and that will not
42
176
  happen as long as the read loop is not done.
43
177
 
44
178
  Fortunately, Polyphony makes it super easy to do more than one thing at once.
45
- Let's spin up a separate coprocess for each client:
179
+ Let's spin up a separate fiber for each client:
46
180
 
47
181
  ```ruby
48
182
  while (client = server.accept)
@@ -50,31 +184,33 @@ while (client = server.accept)
50
184
  end
51
185
  ```
52
186
 
53
- Now, our little program can handle virtually thousands of clients, all with a
54
- little sprinkling of `spin`. Let's discuss how this works. The `Kernel#spin`
55
- method starts a new coprocess, a separate context of execution based on [Ruby
56
- fibers](https://ruby-doc.org/core-2.6.5/Fiber.html). A coprocess may be
57
- arbitrarily suspended and resumed, and Polyphony takes advantage of this fact
58
- to implement a concurrent execution environment without the use of threads.
187
+ Now, our little program can effectively handle thousands of clients, all with a
188
+ little sprinkling of `spin`. The call to `server.accept` suspends the main fiber
189
+ until a connection is made, allowing other fibers to run while it's waiting.
190
+ Likewise, the call to `client.gets` suspends the *client's fiber* until incoming
191
+ data becomes available. Again, all of that is handled automatically by
192
+ Polyphony, and the only hint that our program is concurrent is that little
193
+ innocent call to `#spin`.
59
194
 
60
- The call to `server.accept` suspends the *root coprocess* until a connection is
61
- made, allowing other coprocesses to continue running. Likewise, the call to
62
- `client.gets` suspends the *client's coprocess* until incoming data becomes
63
- available. All this is handled automatically by Polyphony, and the only hint
64
- that our program is concurrent is that innocent call to `spin`.
195
+ Here's a flow chart showing the transfer of control between the different fibers:
65
196
 
66
- Let's consider the advantage of the Polyphony approach:
197
+ <p class="img-figure"><img src="../../assets/img/echo-fibers.svg"></p>
198
+
199
+ Let's consider the advantage of the Polyphony concurrency model:
67
200
 
68
201
  - We didn't need to create custom handler classes with callbacks.
69
202
  - We didn't need to use custom classes or APIs for our networking code.
70
- - Our code is terse, easy to read and - most importantly - expresses the order of events clearly and without being split across callbacks.
71
- - We have a server that can scale to thousands of clients without breaking a sweat.
203
+ - Each task is expressed sequentially. Our code is terse, easy to read and, most
204
+ importantly, expresses the order of events clearly and without having our
205
+ logic split across different methods.
206
+ - We have a server that can scale to thousands of clients without breaking a
207
+ sweat.
72
208
 
73
209
  ## Handling Inactive Connections
74
210
 
75
211
  Now that we have a working concurrent echo server, let's add some bells and
76
212
  whistles. First of all, let's get rid of clients that are not active. We'll do
77
- this by using a Polyphony construct called a cancel scope. Cancel Scope define
213
+ this by using a Polyphony construct called a cancel scope. Cancel scopes define
78
214
  an execution context that can cancel any operation ocurring within its scope:
79
215
 
80
216
  ```ruby
@@ -100,9 +236,10 @@ thus reset the cancel scope timer.
100
236
 
101
237
  In addition, we use an `ensure` block to make sure the client connection is
102
238
  closed, whether or not it was interrupted by the cancel scope timer. The habit
103
- of always cleaning up using `ensure` in the face of interruptions is a
104
- fundamental element of using Polyphony. It makes your code robust, even in a
105
- highly concurrent execution environment.
239
+ of always cleaning up using `ensure` in the face of potential interruptions is a
240
+ fundamental element of using Polyphony correctly. It makes your code robust,
241
+ even in a highly chaotic concurrent execution environment where tasks can be
242
+ interrupted at any time.
106
243
 
107
244
  Here's the complete source code for our Polyphony-based echo server:
108
245
 
@@ -130,8 +267,178 @@ while (client = server.accept)
130
267
  end
131
268
  ```
132
269
 
133
- ## Learning More
270
+ ## Waiting and Interrupting
271
+
272
+ Polyphony makes it very easy to run multiple concurrent fibers. You can
273
+ basically start a fiber for any operation that involves talking to the outside
274
+ world - running a database query, making an HTTP request, sending off a webhook
275
+ invocation etc. While it's trivial to spin off thousands of fibers, we'd also
276
+ like a way to control all those fibers.
277
+
278
+ Polyphony provides a number of tools for controlling fiber execution. Let's
279
+ examine some of these tools and how they work. Suppose we have a fiber that was
280
+ previously spun:
281
+
282
+ ```ruby
283
+ fiber = spin { do_some_work }
284
+ ```
285
+
286
+ We can wait for the fiber to terminate:
287
+
288
+ ```ruby
289
+ fiber.await # alternatively fiber.join
290
+ ```
291
+
292
+ Notice that the await call returns the return value of the fiber block:
293
+
294
+ ```ruby
295
+ fiber = spin { 2 + 2}
296
+ fiber.await #=> 4
297
+ ```
298
+
299
+ We can also stop the fiber at any point:
300
+
301
+ ```ruby
302
+ fiber.stop # or fiber.interrupt
303
+ ```
304
+
305
+ We can inject a return value for the fiber using `stop`:
306
+
307
+ ```ruby
308
+ fiber = spin do
309
+ sleep 1
310
+ 1 + 1
311
+ end
312
+
313
+ spin { puts "1 + 1 = #{fiber.await} wha?" }
314
+
315
+ fiber.stop(3)
316
+ suspend
317
+ ```
318
+
319
+ We can also *cancel* the fiber, which raises a `Polyphony::Cancel` exception:
320
+
321
+ ```ruby
322
+ fiber.cancel!
323
+ ```
324
+
325
+ And finally, we can interrupt the fiber with an exception raised in its current
326
+ context:
327
+
328
+ ```ruby
329
+ fiber.raise 'foo'
330
+ ```
331
+
332
+ For more information on how exceptions are handled in Polyphony, see [exception
333
+ handling](../../technical-overview/exception-handling/).
334
+
335
+ ## Supervising - controlling multiple fibers at once
336
+
337
+ Here's a simple example that we'll use to demonstrate some of the tools provided
338
+ by Polyphony for controlling fibers. Let's build a script that fetches the local
339
+ time for multiple time zones:
340
+
341
+ ```ruby
342
+ require 'polyphony'
343
+ require 'httparty'
344
+ require 'json'
345
+
346
+ def get_time(tzone)
347
+ res = HTTParty.get("http://worldtimeapi.org/api/timezone/#{tzone}")
348
+ json = JSON.parse(res.body)
349
+ Time.parse(json['datetime'])
350
+ end
351
+
352
+ zones = %w{
353
+ Europe/London Europe/Paris Europe/Bucharest America/New_York Asia/Bangkok
354
+ }
355
+ zones.each do |tzone|
356
+ spin do
357
+ time = get_time(tzone)
358
+ puts "Time in #{tzone}: #{time}"
359
+ end
360
+ end
361
+
362
+ suspend
363
+ ```
364
+
365
+ Now that we're familiar with the use of the `#spin` method, we know that all
366
+ those HTTP requests will be processed concurrently, and we can expect those 5
367
+ separate requests to occur within a fraction of a second (depending on our
368
+ machine's location). Also notice how we just used `httparty` with fiber-level
369
+ concurrency, without any boilerplate or employing special wrapper classes.
370
+
371
+ Just as before, we suspend the main fiber after spinning off the worker fibers,
372
+ in order to wait for everything else to be done. But if we needed to do other
373
+ work? For example, we might want to collect the different local times into a
374
+ hash to be processed later. In that case, we can use a `Supervisor`:
375
+
376
+ ```ruby
377
+ def get_times(zones)
378
+ Polyphony::Supervisor.new do |s|
379
+ zones.each do |tzone|
380
+ s.spin { [tzone, get_time(tzone)] }
381
+ end
382
+ end
383
+ end
384
+
385
+ get_times(zones).await.each do |tzone, time|
386
+ puts "Time in #{tzone}: #{time}"
387
+ end
388
+ ```
389
+
390
+ There's quite a bit going on here, so let's break it down. We first construct a
391
+ supervisor and spin our fibers in its context using `Supervisor#spin`.
392
+
393
+ ```ruby
394
+ Polyphony::Supervisor.new do |s|
395
+ ...
396
+ s.spin { ... }
397
+ ...
398
+ end
399
+ ```
400
+
401
+ Once our worker fibers are spun, the supervisor can be used to control them. We
402
+ can wait for all fibers to terminate using `Supervisor#await`, which returns an
403
+ array with the return values of all fibers (in the above example, each fiber
404
+ returns the time zone and the local time).
405
+
406
+ ```ruby
407
+ results = supervisor.await
408
+ ```
409
+
410
+ We can also select the result of the first fiber that has finished executing.
411
+ All the other fibers will be interrupted:
412
+
413
+ ```ruby
414
+ result, fiber = supervisor.select
415
+ ```
416
+
417
+ (Notice how `Supervisor#select` returns both the fiber's return value and the
418
+ fiber itself).
419
+
420
+ We can also interrupt all the supervised fibers by using `Supervisor#interrupt`
421
+ (or `#stop`) just like with single fibers:
422
+
423
+ ```ruby
424
+ supervisor.interrupt
425
+ ```
426
+
427
+ ## What Else Can I Do with Polyphony?
428
+
429
+ Polyphony currently provides support for any library that uses Ruby's stock
430
+ `socket` and `openssl` classes. Polyphony also includes adapters for the `pg`,
431
+ `redis` and `irb` gems. It also includes an implementation of an integrated HTTP
432
+ 1 / HTTP 2 / websockets web server with support for TLS termination, ALPN
433
+ protocol selection and preliminary rack support.
434
+
435
+ ## Fibers are the Future!
134
436
 
135
- Polyphony is still new, and the present documentation is far from being
136
- complete. For more information read the [technical overview](technical-overview/concurrency.md)
137
- or look at the [included examples](#).
437
+ Implementing concurrency at the level of fibers opens up so many new
438
+ possibilities for Ruby. Polyphony has the performance characteristics and
439
+ provides the necessary tools for transforming how concurrent Ruby apps are
440
+ written. Polyphony is still new, and the present documentation is far from being
441
+ complete. To learn more about Polyphony, read the [technical
442
+ overview](../../technical-overview/design-principles/). For more examples please
443
+ consult the [Github
444
+ repository](https://github.com/digital-fabric/polyphony/tree/master/examples).
@@ -0,0 +1,10 @@
1
+ ---
2
+ layout: page
3
+ title: Getting Started
4
+ description: Lorem ipsum
5
+ has_children: true
6
+ section: true
7
+ has_toc: false
8
+ nav_order: 2
9
+ section_link: /getting-started/installing
10
+ ---
data/docs/index.md ADDED
@@ -0,0 +1,91 @@
1
+ ---
2
+ layout: page
3
+ title: Home
4
+ nav_order: 1
5
+ description: Lorem ipsum
6
+ permalink: /
7
+ ---
8
+
9
+ # Polyphony - fine-grained concurrency for Ruby
10
+
11
+ > Polyphony \| pəˈlɪf\(ə\)ni \|
12
+ > 1. _Music_ the style of simultaneously combining a number of parts, each
13
+ > forming an individual melody and harmonizing with each other.
14
+ > 2. _Programming_ a Ruby gem for concurrent programming focusing on performance
15
+ > and developer happiness.
16
+
17
+ Polyphony is a library for building concurrent applications in Ruby. Polyphony
18
+ harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html)
19
+ to provide a cooperative, sequential coroutine-based concurrency model. Under
20
+ the hood, Polyphony uses [libev](https://github.com/enki/libev) as a
21
+ high-performance event reactor that provides timers, I/O watchers and other
22
+ asynchronous event primitives.
23
+
24
+
25
+ ## Focused on Developer Happiness
26
+
27
+ Polyphony is designed to make concurrent Ruby programming feel natural and
28
+ fluent. Polyphony reduces the boilerplate usually associated with concurrent
29
+ programming, and introduces concurrency primitives that are easy to use, easy to
30
+ understand, and above all idiomatic.
31
+
32
+ ## Optimized for High Performance
33
+
34
+ Polyphony offers high performance for I/O bound Ruby apps. Distributing
35
+ concurrent tasks over fibers, instead of threads or processes, minimizes memory
36
+ consumption and reduces the cost of context-switching.
37
+
38
+ ## Designed for Interoperability
39
+
40
+ Polyphony makes it possible to use normal Ruby built-in classes like `IO`, and
41
+ `Socket` in a concurrent multi-fiber environment. Polyphony takes care of
42
+ context-switching automatically whenever a blocking call like `Socket#accept`,
43
+ `IO#read` or `Kernel#sleep` is issued.
44
+
45
+ ## A Growing Ecosystem
46
+
47
+ Polyphony includes a full-blown HTTP server implementation with integrated
48
+ support for HTTP 1, HTTP 2 and WebSockets, TLS/SSL termination, automatic
49
+ ALPN protocol selection, and body streaming. Polyphony also includes fiber-aware
50
+ extensions for PostgreSQL and Redis. More databases and services are forthcoming.
51
+
52
+ ## Features
53
+
54
+ * Co-operative scheduling of concurrent tasks using Ruby fibers.
55
+ * High-performance event reactor for handling I/O events and timers.
56
+ * Natural, sequential programming style that makes it easy to reason about
57
+ concurrent code.
58
+ * Abstractions and constructs for controlling the execution of concurrent code:
59
+ supervisors, cancel scopes, throttling, resource pools etc.
60
+ * Code can use native networking classes and libraries, growing support for
61
+ third-party gems such as `pg` and `redis`.
62
+ * Use stdlib classes such as `TCPServer` and `TCPSocket` and `Net::HTTP`.
63
+ * Competitive performance and scalability characteristics, in terms of both
64
+ throughput and memory consumption.
65
+
66
+ ## Prior Art
67
+
68
+ Polyphony draws inspiration from the following, in no particular order:
69
+
70
+ * [nio4r](https://github.com/socketry/nio4r/) and
71
+ [async](https://github.com/socketry/async) (Polyphony's C-extension code is
72
+ largely a spinoff of
73
+ [nio4r's](https://github.com/socketry/nio4r/tree/master/ext))
74
+ * The [go scheduler](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)
75
+ * [EventMachine](https://github.com/eventmachine/eventmachine)
76
+ * [Trio](https://trio.readthedocs.io/)
77
+ * [Erlang supervisors](http://erlang.org/doc/man/supervisor.html) (and actually,
78
+ Erlang in general)
79
+
80
+ ## Going further
81
+
82
+ To learn more about using Polyphony to build concurrent applications, read the
83
+ technical overview below, or look at the [included
84
+ examples](https://github.com/digital-fabric/polyphony/tree/9e0f3b09213156bdf376ef33684ef267517f06e8/examples/README.md).
85
+ A thorough reference is forthcoming.
86
+
87
+ ## Contributing to Polyphony
88
+
89
+ Issues and pull requests will be gladly accepted. Please use the git repository
90
+ at https://github.com/digital-fabric/polyphony as your primary point of
91
+ departure for contributing.
@@ -1,28 +1,86 @@
1
+ ---
2
+ layout: page
3
+ title: Concurrency the Easy Way
4
+ nav_order: 2
5
+ parent: Technical Overview
6
+ permalink: /technical-overview/concurrency/
7
+ ---
1
8
  # Concurrency the Easy Way
2
9
 
3
- Concurrency is a major consideration for modern programmers. Applications and digital platforms are nowadays expected to do multiple things at once: serve multiple clients, process multiple background jobs, talk to multiple external services. Concurrency is the property of our programming environment allowing us to schedule and control multiple ongoing operations.
4
-
5
- Traditionally, concurrency has been achieved by using multiple processes or threads. Both approaches have proven problematic. Processes consume relatively a lot of memory, and are relatively difficult to coordinate. Threads consume less memory than processes and make it difficult to synchronize access to shared resources, often leading to race conditions and memory corruption. Using threads often necessitates either using special-purpose thread-safe data structures, or otherwise protecting shared resource access using mutexes and critical sections. In addition, dynamic languages such as Ruby and Python will synchronize multiple threads using a global interpreter lock, which means thread execution cannot be parallelized. Furthermore, the amount of threads and processes on a single system is relatively limited, to the order of several hundreds or a few thousand at most.
6
-
7
- Polyphony offers a third way to write concurrent programs, by using a Ruby construct called [fibers](https://ruby-doc.org/core-2.6.5/Fiber.html). Fibers, based on the idea of [coroutines](https://en.wikipedia.org/wiki/Coroutine), provide a way to run a computation that can be suspended and resumed at any moment. For example, a computation waiting for a reply from a database can suspend itself, transferring control to another ongoing computation, and be resumed once the database has sent back its reply. Meanwhile, another computation is started that opens a socket to a remote service, and then suspends itself, waiting for the connection to be established.
8
-
9
- This form of concurrency, called cooperative concurrency \(in contrast to pre-emptive concurrency, like threads and processes\), offers many advantages, especially for applications that are [I/O bound](https://en.wikipedia.org/wiki/I/O_bound). Fibers are very lightweight \(starting at about 20KB\), can be context-switched faster than threads or processes, and literally millions of them can be created on a single system - the only limiting factor is available memory.
10
-
11
- Polyphony takes Ruby's fibers and adds a way to schedule and switch between fibers automatically whenever a blocking operation is started, such as waiting for a TCP connection to be established, or waiting for an I/O object to be readable, or waiting for a timer to elapse. In addition, Polyphony patches the stock Ruby classes to support its concurrency model, letting developers use all of Ruby's stdlib, for example `Net::HTTP` and `Mail` while reaping the benefits of lightweight, highly performant, fiber-based concurrency.
12
-
13
- Writing concurrent applications using Polyphony's fiber-based concurrency model offers a significant performance advantage. Computational tasks can be broken down into many fine-grained concurrent operations that cost very little in memory and context-switching time. More importantly, this concurrency model lets developers express their ideas in a sequential manner, leading to source code that is easy to read and reason about.
10
+ Concurrency is a major consideration for modern programmers. Applications and
11
+ digital platforms are nowadays expected to do multiple things at once: serve
12
+ multiple clients, process multiple background jobs, talk to multiple external
13
+ services. Concurrency is the property of our programming environment allowing us
14
+ to schedule and control multiple ongoing operations.
15
+
16
+ Traditionally, concurrency has been achieved by using multiple processes or
17
+ threads. Both approaches have proven problematic. Processes consume relatively a
18
+ lot of memory, and are relatively difficult to coordinate. Threads consume less
19
+ memory than processes and make it difficult to synchronize access to shared
20
+ resources, often leading to race conditions and memory corruption. Using threads
21
+ often necessitates either using special-purpose thread-safe data structures, or
22
+ otherwise protecting shared resource access using mutexes and critical sections.
23
+ In addition, dynamic languages such as Ruby and Python will synchronize multiple
24
+ threads using a global interpreter lock, which means thread execution cannot be
25
+ parallelized. Furthermore, the amount of threads and processes on a single
26
+ system is relatively limited, to the order of several hundreds or a few thousand
27
+ at most.
28
+
29
+ Polyphony offers a third way to write concurrent programs, by using a Ruby
30
+ construct called [fibers](https://ruby-doc.org/core-2.6.5/Fiber.html). Fibers,
31
+ based on the idea of [coroutines](https://en.wikipedia.org/wiki/Coroutine),
32
+ provide a way to run a computation that can be suspended and resumed at any
33
+ moment. For example, a computation waiting for a reply from a database can
34
+ suspend itself, transferring control to another ongoing computation, and be
35
+ resumed once the database has sent back its reply. Meanwhile, another
36
+ computation is started that opens a socket to a remote service, and then
37
+ suspends itself, waiting for the connection to be established.
38
+
39
+ This form of concurrency, called cooperative concurrency \(in contrast to
40
+ pre-emptive concurrency, like threads and processes\), offers many advantages,
41
+ especially for applications that are [I/O
42
+ bound](https://en.wikipedia.org/wiki/I/O_bound). Fibers are very lightweight
43
+ \(starting at about 20KB\), can be context-switched faster than threads or
44
+ processes, and literally millions of them can be created on a single system -
45
+ the only limiting factor is available memory.
46
+
47
+ Polyphony takes Ruby's fibers and adds a way to schedule and switch between
48
+ fibers automatically whenever a blocking operation is started, such as waiting
49
+ for a TCP connection to be established, or waiting for an I/O object to be
50
+ readable, or waiting for a timer to elapse. In addition, Polyphony patches the
51
+ stock Ruby classes to support its concurrency model, letting developers use all
52
+ of Ruby's stdlib, for example `Net::HTTP` and `Mail` while reaping the benefits
53
+ of lightweight, highly performant, fiber-based concurrency.
54
+
55
+ Writing concurrent applications using Polyphony's fiber-based concurrency model
56
+ offers a significant performance advantage. Computational tasks can be broken
57
+ down into many fine-grained concurrent operations that cost very little in
58
+ memory and context-switching time. More importantly, this concurrency model lets
59
+ developers express their ideas in a sequential manner, leading to source code
60
+ that is easy to read and reason about.
14
61
 
15
62
  ## Fibers - Polyphony's basic unit of concurrency
16
63
 
17
- Polyphony extends the core `Fiber` class with additional functionality that allows scheduling, synchronizing, interrupting and otherwise controlling running fibers. Polyphony makes sure any exception raised while a is running is [handled correctly](exception-handling.md). Moreover, fibers can communicate with each other using message passing, turning them into autonomous actors in a fine-grained concurrent environment.
64
+ Polyphony extends the core `Fiber` class with additional functionality that
65
+ allows scheduling, synchronizing, interrupting and otherwise controlling running
66
+ fibers. Polyphony makes sure any exception raised while a is running is [handled
67
+ correctly](exception-handling.md). Moreover, fibers can communicate with each
68
+ other using message passing, turning them into autonomous actors in a
69
+ fine-grained concurrent environment.
18
70
 
19
71
  ## Higher-Order Concurrency Constructs
20
72
 
21
- Polyphony also provides several methods and constructs for controlling multiple fibers. Methods like `cancel_after` and `move_on_after` allow interrupting a fiber that's blocking on any arbitrary operation.
73
+ Polyphony also provides several methods and constructs for controlling multiple
74
+ fibers. Methods like `cancel_after` and `move_on_after` allow interrupting a
75
+ fiber that's blocking on any arbitrary operation.
22
76
 
23
- Cancel scopes \(borrowed from the brilliant Python library [Trio](https://trio.readthedocs.io/en/stable/)\) allows cancelling ongoing operations for any reason with more control over cancelling behaviour.
77
+ Cancel scopes \(borrowed from the brilliant Python library
78
+ [Trio](https://trio.readthedocs.io/en/stable/)\) allows cancelling ongoing
79
+ operations for any reason with more control over cancelling behaviour.
24
80
 
25
- Supervisors allow controlling multiple fibers. They offer enhanced exception handling and can be nested to create complex supervision trees ala [Erlang](https://adoptingerlang.org/docs/development/supervision_trees/).
81
+ Supervisors allow controlling multiple fibers. They offer enhanced exception
82
+ handling and can be nested to create complex supervision trees ala
83
+ [Erlang](https://adoptingerlang.org/docs/development/supervision_trees/).
26
84
 
27
85
  Some other constructs offered by Polyphony:
28
86
 
@@ -41,5 +99,9 @@ Some other constructs offered by Polyphony:
41
99
 
42
100
  — Yukihiro “Matz” Matsumoto
43
101
 
44
- Polyphony's goal is to make programmers even happier by offering them an easy way to write concurrent applications in Ruby. Polyphony aims to show that Ruby can be used for developing sufficiently high-performance applications, while offering all the advantages of Ruby, with source code that is easy to read and understand.
102
+ Polyphony's goal is to make programmers even happier by offering them an easy
103
+ way to write concurrent applications in Ruby. Polyphony aims to show that Ruby
104
+ can be used for developing sufficiently high-performance applications, while
105
+ offering all the advantages of Ruby, with source code that is easy to read and
106
+ understand.
45
107
 
@@ -1,3 +1,10 @@
1
+ ---
2
+ layout: page
3
+ title: Design Principles
4
+ nav_order: 1
5
+ parent: Technical Overview
6
+ permalink: /technical-overview/design-principles/
7
+ ---
1
8
  # Design Principles
2
9
 
3
10
  Polyphony was created in order to enable creating high-performance concurrent