polyphony 0.43.8
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 +7 -0
- data/.gitbook.yaml +4 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +59 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +393 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +141 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +26 -0
- data/TODO.md +201 -0
- data/bin/polyphony-debug +87 -0
- data/docs/_config.yml +64 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/_sass/overrides.scss +0 -0
- data/docs/_user-guide/all-about-timers.md +126 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/_user-guide/web-server.md +136 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +425 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +195 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +34 -0
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +359 -0
- data/docs/index.md +94 -0
- data/docs/main-concepts/concurrency.md +151 -0
- data/docs/main-concepts/design-principles.md +161 -0
- data/docs/main-concepts/exception-handling.md +291 -0
- data/docs/main-concepts/extending.md +89 -0
- data/docs/main-concepts/fiber-scheduling.md +197 -0
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/pg_client.rb +36 -0
- data/examples/adapters/pg_notify.rb +35 -0
- data/examples/adapters/pg_pool.rb +43 -0
- data/examples/adapters/pg_transaction.rb +31 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/adapters/redis_channels.rb +122 -0
- data/examples/adapters/redis_client.rb +19 -0
- data/examples/adapters/redis_pubsub.rb +26 -0
- data/examples/adapters/redis_pubsub_perf.rb +68 -0
- data/examples/core/01-spinning-up-fibers.rb +18 -0
- data/examples/core/02-awaiting-fibers.rb +20 -0
- data/examples/core/03-interrupting.rb +39 -0
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-channels.rb +45 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-deferring-an-operation.rb +14 -0
- data/examples/core/xx-erlang-style-genserver.rb +81 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-forking.rb +24 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-recurrent-timer.rb +12 -0
- data/examples/core/xx-resource_delegate.rb +31 -0
- data/examples/core/xx-signals.rb +16 -0
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-sleeping.rb +25 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-spin_error_backtrace.rb +33 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-stop.rb +20 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/examples/core/xx-supervisors.rb +21 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/core/xx-thread_pool.rb +17 -0
- data/examples/core/xx-throttling.rb +18 -0
- data/examples/core/xx-timeout.rb +10 -0
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/core/xx-trace.rb +79 -0
- data/examples/core/xx-using-a-mutex.rb +21 -0
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-backticks.rb +11 -0
- data/examples/io/xx-echo_client.rb +25 -0
- data/examples/io/xx-echo_client_from_stdin.rb +21 -0
- data/examples/io/xx-echo_pipe.rb +16 -0
- data/examples/io/xx-echo_server.rb +17 -0
- data/examples/io/xx-echo_server_with_timeout.rb +34 -0
- data/examples/io/xx-echo_stdin.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +36 -0
- data/examples/io/xx-httparty.rb +38 -0
- data/examples/io/xx-irb.rb +17 -0
- data/examples/io/xx-net-http.rb +15 -0
- data/examples/io/xx-open.rb +16 -0
- data/examples/io/xx-switch.rb +15 -0
- data/examples/io/xx-system.rb +11 -0
- data/examples/io/xx-tcpserver.rb +15 -0
- data/examples/io/xx-tcpsocket.rb +18 -0
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/fs_read.rb +38 -0
- data/examples/performance/mem-usage.rb +56 -0
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +33 -0
- data/examples/performance/snooze.rb +39 -0
- data/examples/performance/snooze_raw.rb +39 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
- data/examples/performance/thread_pool_perf.rb +63 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/polyphony/extconf.rb +20 -0
- data/ext/polyphony/fiber.c +109 -0
- data/ext/polyphony/libev.c +2 -0
- data/ext/polyphony/libev.h +9 -0
- data/ext/polyphony/libev_agent.c +882 -0
- data/ext/polyphony/polyphony.c +71 -0
- data/ext/polyphony/polyphony.h +97 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +208 -0
- data/ext/polyphony/tracing.c +11 -0
- data/lib/polyphony.rb +136 -0
- data/lib/polyphony/adapters/fs.rb +19 -0
- data/lib/polyphony/adapters/irb.rb +52 -0
- data/lib/polyphony/adapters/postgres.rb +110 -0
- data/lib/polyphony/adapters/process.rb +33 -0
- data/lib/polyphony/adapters/redis.rb +67 -0
- data/lib/polyphony/adapters/trace.rb +138 -0
- data/lib/polyphony/core/channel.rb +46 -0
- data/lib/polyphony/core/exceptions.rb +36 -0
- data/lib/polyphony/core/global_api.rb +124 -0
- data/lib/polyphony/core/resource_pool.rb +117 -0
- data/lib/polyphony/core/sync.rb +21 -0
- data/lib/polyphony/core/thread_pool.rb +64 -0
- data/lib/polyphony/core/throttler.rb +41 -0
- data/lib/polyphony/event.rb +17 -0
- data/lib/polyphony/extensions/core.rb +174 -0
- data/lib/polyphony/extensions/fiber.rb +379 -0
- data/lib/polyphony/extensions/io.rb +221 -0
- data/lib/polyphony/extensions/openssl.rb +81 -0
- data/lib/polyphony/extensions/socket.rb +150 -0
- data/lib/polyphony/extensions/thread.rb +108 -0
- data/lib/polyphony/net.rb +77 -0
- data/lib/polyphony/version.rb +5 -0
- data/polyphony.gemspec +40 -0
- data/test/coverage.rb +54 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +56 -0
- data/test/q.rb +24 -0
- data/test/run.rb +5 -0
- data/test/stress.rb +25 -0
- data/test/test_agent.rb +130 -0
- data/test/test_event.rb +59 -0
- data/test/test_ext.rb +196 -0
- data/test/test_fiber.rb +988 -0
- data/test/test_global_api.rb +352 -0
- data/test/test_io.rb +249 -0
- data/test/test_kernel.rb +57 -0
- data/test/test_process_supervision.rb +46 -0
- data/test/test_queue.rb +112 -0
- data/test/test_resource_pool.rb +138 -0
- data/test/test_signal.rb +100 -0
- data/test/test_socket.rb +34 -0
- data/test/test_supervise.rb +103 -0
- data/test/test_thread.rb +170 -0
- data/test/test_thread_pool.rb +101 -0
- data/test/test_throttler.rb +50 -0
- data/test/test_trace.rb +68 -0
- metadata +482 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Tutorial
|
4
|
+
parent: Getting Started
|
5
|
+
nav_order: 3
|
6
|
+
---
|
7
|
+
|
8
|
+
# Tutorial
|
9
|
+
{: .no_toc }
|
10
|
+
|
11
|
+
## Table of contents
|
12
|
+
{: .no_toc .text-delta }
|
13
|
+
|
14
|
+
- TOC
|
15
|
+
{:toc}
|
16
|
+
|
17
|
+
---
|
18
|
+
|
19
|
+
Polyphony is a new Ruby library aimed at making writing concurrent Ruby apps
|
20
|
+
easy and fun. In this article, we'll introduce Polyphony's fiber-based
|
21
|
+
concurrency model, some of Polyphony's API, and demonstrate how to solve some
|
22
|
+
simple situations related to concurrent computing.
|
23
|
+
|
24
|
+
## What are Fibers and What are They Good For?
|
25
|
+
|
26
|
+
Fibers are some of Ruby's most underappreciated hidden gems. Up until now,
|
27
|
+
fibers have been used mostly as the underlying mechanism for implementing
|
28
|
+
lazy enumerators and asynchronous generators. Fibers encapsulate, in short,
|
29
|
+
an execution context that can be paused and resumed at will.
|
30
|
+
|
31
|
+
Fibers are also at the heart of Polyphony's concurrency model. Polyphony employs
|
32
|
+
fibers as a way to run multiple tasks at once, each task advancing at its own
|
33
|
+
pace, pausing when waiting for an event to occur, and automatically resuming
|
34
|
+
when that event has occurred.
|
35
|
+
|
36
|
+
Take for example a web app: in order to fulfil an incoming request, multiple
|
37
|
+
steps are required: querying the database, fetching cached entries from Redis,
|
38
|
+
talking to third-party services such as Twilio or AWS S3. Each step can last
|
39
|
+
tens of milliseconds, and blocks the current thread. Such an app is said to be
|
40
|
+
I/O-bound, that is, it mostly spends its time waiting for some external
|
41
|
+
services.
|
42
|
+
|
43
|
+
The traditional approach to handling multiple requests concurrently is to employ
|
44
|
+
multiple threads or processes, but this approach has numerous disavantages:
|
45
|
+
|
46
|
+
- Both threads and processes are heavyweight, in both memory consmption and
|
47
|
+
the cost associated with context-switching.
|
48
|
+
- Threads introduce hard-to-debug race conditions, and do not offer true
|
49
|
+
parallelism, owing to Ruby's GVL.
|
50
|
+
- Processes are more difficult to coordinate, since they do not share memory.
|
51
|
+
- Both threads and processes are limited to a few thousand at best on a single
|
52
|
+
machine. Trying to spawn a thread per client essentially limits the scaling
|
53
|
+
capacity of your system.
|
54
|
+
|
55
|
+
Polyphony eschews both threads and processes in favor of fibers as the basic
|
56
|
+
unit of concurrency. The idea is that any time a blocking I/O operation occurs,
|
57
|
+
the current fiber is paused, and another fiber which has been marked as
|
58
|
+
*runnable* is resumed. This way, your Ruby code can keep on handling incoming
|
59
|
+
HTTP requests as they come with a scaling capacity that is virtually only
|
60
|
+
limited by available memory.
|
61
|
+
|
62
|
+
## Switchpoints and the Fiber-Switching Dance
|
63
|
+
|
64
|
+
In order to make pausing and resuming fibers completely automatic and painfree,
|
65
|
+
we need to know when an operation is going to block, and when it can be
|
66
|
+
completed without blocking. Operations that might block execution are considered
|
67
|
+
*switchpoints*. A switchpoint is a point in time at which control might switch
|
68
|
+
from the currently running fiber to another fiber that is in a runnable state.
|
69
|
+
Switchpoints may occur in any of the following cases:
|
70
|
+
|
71
|
+
- On a call to any blocking operation, such as `#sleep`, `Fiber#await`,
|
72
|
+
`Thread#join` etc.
|
73
|
+
- On fiber termination
|
74
|
+
- On a call to `#suspend`
|
75
|
+
- On a call to `#snooze`
|
76
|
+
- On a call to `Thread#switch_fiber`
|
77
|
+
|
78
|
+
At any switchpoint, the following takes place:
|
79
|
+
|
80
|
+
- Check if any fiber is runnable, that is, ready to continue processing.
|
81
|
+
- If no fiber is runnable, watch for events (see below) and wait for at least
|
82
|
+
one fiber to become runnable.
|
83
|
+
- Pause the current fiber and switch to the first runnable fiber, which resumes
|
84
|
+
at the point it was last paused.
|
85
|
+
|
86
|
+
The automatic switching between fibers is complemented by employing
|
87
|
+
[libev](http://software.schmorp.de/pkg/libev.html), a multi-platform high
|
88
|
+
performance event reactor that allows listening to I/O, timer and other events.
|
89
|
+
At every switchpoint where no fibers are runnable, the libev evet loop is run
|
90
|
+
until events occur, which in turn cause the relevant fibers to become runnable.
|
91
|
+
|
92
|
+
Let's examine a simple example:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
require 'polyphony'
|
96
|
+
|
97
|
+
spin do
|
98
|
+
puts "Going to sleep..."
|
99
|
+
sleep 1
|
100
|
+
puts "Woke up"
|
101
|
+
end
|
102
|
+
|
103
|
+
suspend
|
104
|
+
puts "We're done"
|
105
|
+
```
|
106
|
+
|
107
|
+
The above program does nothing exceptional, it just sleeps for 1 second and
|
108
|
+
prints a bunch of messages. But it is enough to demonstrate how concurrency
|
109
|
+
works in Polyphony. Here's a flow chart of the transfer of control:
|
110
|
+
|
111
|
+
<p class="img-figure"><img src="../../assets/img/sleeping-fiber.svg"></p>
|
112
|
+
|
113
|
+
Here's the actual sequence of execution (in pseudo-code)
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
# (main fiber)
|
117
|
+
sleeper = spin { ... } # The main fiber spins up a new fiber marked as runnable
|
118
|
+
suspend # The main fiber suspends, waiting for all other work to finish
|
119
|
+
Thread.current.switch_fiber # Polyphony looks for other runnable fibers
|
120
|
+
|
121
|
+
# (sleeper fiber)
|
122
|
+
puts "Going to sleep..." # The sleeper fiber starts running
|
123
|
+
sleep 1 # The sleeper fiber goes to sleep
|
124
|
+
Gyro::Timer.new(1, 0).await # A timer event watcher is setup and yields
|
125
|
+
Thread.current.switch_fiber # Polyphony looks for other runnable fibers
|
126
|
+
Thread.current.agent.poll # With no work left, the event loop is ran
|
127
|
+
fiber.schedule # The timer event fires, scheduling the sleeper fiber
|
128
|
+
# <= The sleep method returns
|
129
|
+
puts "Woke up"
|
130
|
+
Thread.current.switch_fiber # With the fiber done, Polyphony looks for work
|
131
|
+
|
132
|
+
# with no more work, control is returned to the main fiber
|
133
|
+
# (main fiber)
|
134
|
+
# <=
|
135
|
+
# With no more work left, the main fiber is resumed and the suspend call returns
|
136
|
+
puts "We're done"
|
137
|
+
```
|
138
|
+
|
139
|
+
What we have done in fact is we multiplexed two different contexts of execution
|
140
|
+
(fibers) onto a single thread, each fiber continuing at its own pace and
|
141
|
+
yielding control when waiting for something to happen. This context-switching
|
142
|
+
dance, performed automatically by Polyphony behind the scenes, enables building
|
143
|
+
highly-concurrent Ruby apps, with minimal impact on performance.
|
144
|
+
|
145
|
+
## Building a Simple Echo Server with Polyphony
|
146
|
+
|
147
|
+
Let's now turn our attention to something a bit more useful: a concurrent echo
|
148
|
+
server. Our server will accept TCP connections and send back whatever it receives
|
149
|
+
from the client.
|
150
|
+
|
151
|
+
We'll start by opening a server socket:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
require 'polyphony'
|
155
|
+
|
156
|
+
server = TCPServer.open('127.0.0.1', 1234)
|
157
|
+
puts 'Echoing on port 1234...'
|
158
|
+
```
|
159
|
+
|
160
|
+
Next, we'll add a loop accepting connections:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
while (client = server.accept)
|
164
|
+
handle_client(client)
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
The `handle_client` method is almost trivial:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
def handle_client(client)
|
172
|
+
while (data = client.gets)
|
173
|
+
client << data
|
174
|
+
end
|
175
|
+
rescue Errno::ECONNRESET
|
176
|
+
puts 'Connection reset by client'
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
## Adding Concurrency
|
181
|
+
|
182
|
+
Up until now, we did nothing about concurrency. In fact, our code will not be
|
183
|
+
able to handle more than one client at a time, because the accept loop cannot
|
184
|
+
continue to run until the call to `#handle_client` returns, and that will not
|
185
|
+
happen as long as the read loop is not done.
|
186
|
+
|
187
|
+
Fortunately, Polyphony makes it super easy to do more than one thing at once.
|
188
|
+
Let's spin up a separate fiber for each client:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
while (client = server.accept)
|
192
|
+
spin { handle_client(client) }
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
Now, our little program can effectively handle thousands of clients, all with a
|
197
|
+
little sprinkling of `spin`. The call to `server.accept` suspends the main fiber
|
198
|
+
until a connection is made, allowing other fibers to run while it's waiting.
|
199
|
+
Likewise, the call to `client.gets` suspends the *client's fiber* until incoming
|
200
|
+
data becomes available. Again, all of that is handled automatically by
|
201
|
+
Polyphony, and the only hint that our program is concurrent is that little
|
202
|
+
innocent call to `#spin`.
|
203
|
+
|
204
|
+
Here's a flow chart showing the transfer of control between the different fibers:
|
205
|
+
|
206
|
+
<p class="img-figure"><img src="../../assets/img/echo-fibers.svg"></p>
|
207
|
+
|
208
|
+
Let's consider the advantage of the Polyphony concurrency model:
|
209
|
+
|
210
|
+
- We didn't need to create custom handler classes with callbacks.
|
211
|
+
- We didn't need to use custom classes or APIs for our networking code.
|
212
|
+
- Each task is expressed sequentially. Our code is terse, easy to read and, most
|
213
|
+
importantly, expresses the order of events clearly and without having our
|
214
|
+
logic split across different methods.
|
215
|
+
- We have a server that can scale to thousands of clients without breaking a
|
216
|
+
sweat.
|
217
|
+
|
218
|
+
## Handling Inactive Connections
|
219
|
+
|
220
|
+
Now that we have a working concurrent echo server, let's add some bells and
|
221
|
+
whistles. First of all, let's get rid of clients that are not active. We'll do
|
222
|
+
this by setting up a timeout fiber that cancels the fiber dealing with the connection
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
def handle_client(client)
|
226
|
+
timeout = cancel_after(10)
|
227
|
+
while (data = client.gets)
|
228
|
+
timeout.restart
|
229
|
+
client << data
|
230
|
+
end
|
231
|
+
rescue Polyphony::Cancel
|
232
|
+
client.puts 'Closing connection due to inactivity.'
|
233
|
+
rescue Errno::ECONNRESET
|
234
|
+
puts 'Connection reset by client'
|
235
|
+
ensure
|
236
|
+
client.close
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
The call to `#cancel_after` spins up a new fiber that will sleep for 10 seconds,
|
241
|
+
then cancel its parent. The call to `client.gets` blocks until new data is
|
242
|
+
available. If no new data is available, the `timeout` fiber will finish
|
243
|
+
sleeping, and then cancel the client handling fiber by raising a
|
244
|
+
`Polyphony::Cancel` exception. However, if new data is received, the `timeout`
|
245
|
+
fiber is restarted, causing to begin sleeping again for 10 seconds. If the
|
246
|
+
client has closed the connection, or some other exception occurs, the `timeout`
|
247
|
+
fiber is automatically stopped as it is a child of the client handling fiber.
|
248
|
+
|
249
|
+
The habit of always cleaning up using `ensure` in the face of potential
|
250
|
+
interruptions is a fundamental element of using Polyphony correctly. This makes
|
251
|
+
your code robust, even in a highly chaotic concurrent execution environment
|
252
|
+
where tasks can be started, restarted and interrupted at any time.
|
253
|
+
|
254
|
+
## Implementing graceful shutdown
|
255
|
+
|
256
|
+
Let's now add graceful shutdown to our server. This means that when the server
|
257
|
+
is stopped we'll first stop accepting new connections, but we'll let any already
|
258
|
+
connected clients keep their sessions.
|
259
|
+
|
260
|
+
Polyphony's concurrency model is structured. Fibers are limited to the lifetime
|
261
|
+
of their direct parent. When the main fiber terminates (on program exit), it
|
262
|
+
will terminate all its child fibers, each of which will in turn terminate its
|
263
|
+
own children. The termination of child fibers is implemented by sending each
|
264
|
+
child fiber a `Polyphony::Terminate` exception. We can implement custom
|
265
|
+
termination logic simply by adding an exception handler for
|
266
|
+
`Polyphony::Terminate`:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# We first refactor the echo loop into a method
|
270
|
+
def client_loop(client, timeout = nil)
|
271
|
+
while (data = client.gets)
|
272
|
+
timeout&.reset
|
273
|
+
client << data
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def handle_client(client)
|
278
|
+
timeout = cancel_after(10)
|
279
|
+
client_loop(client, timeout)
|
280
|
+
rescue Polyphony::Cancel
|
281
|
+
client.puts 'Closing connection due to inactivity.'
|
282
|
+
rescue Polyphony::Terminate
|
283
|
+
# We add a handler for the Terminate exception, and give
|
284
|
+
client.puts 'Server is shutting down. You have 5 more seconds...'
|
285
|
+
move_on_after(5) do
|
286
|
+
client_loop(client)
|
287
|
+
end
|
288
|
+
rescue Errno::ECONNRESET
|
289
|
+
puts 'Connection reset by client'
|
290
|
+
ensure
|
291
|
+
timeout.stop
|
292
|
+
client.close
|
293
|
+
end
|
294
|
+
```
|
295
|
+
|
296
|
+
## Conclusion
|
297
|
+
|
298
|
+
In this tutorial, we have shown how Polyphony can be used to create robust,
|
299
|
+
highly concurrent Ruby applications. As we have discussed above, Polyphony
|
300
|
+
provides a comprehensive set of tools that make it simple and intuitive to write
|
301
|
+
concurrent applications, with features such as structured concurrency (for
|
302
|
+
controlling fiber lifetime), timeouts (for handling inactive or slow clients),
|
303
|
+
custom termination logic (for implementing graceful shutdown).
|
304
|
+
|
305
|
+
Here's the complete source code for our Polyphony-based echo server:
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
require 'polyphony/auto_run'
|
309
|
+
|
310
|
+
server = TCPServer.open('127.0.0.1', 1234)
|
311
|
+
puts 'Echoing on port 1234...'
|
312
|
+
|
313
|
+
def client_loop(client, timeout = nil)
|
314
|
+
while (data = client.gets)
|
315
|
+
timeout&.reset
|
316
|
+
client << data
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def handle_client(client)
|
321
|
+
timeout = cancel_after(10)
|
322
|
+
client_loop(client, timeout)
|
323
|
+
rescue Polyphony::Cancel
|
324
|
+
client.puts 'Closing connection due to inactivity.'
|
325
|
+
rescue Polyphony::Terminate
|
326
|
+
client.puts 'Server is shutting down. You have 5 more seconds...'
|
327
|
+
move_on_after(5) do
|
328
|
+
client_loop(client)
|
329
|
+
end
|
330
|
+
rescue Errno::ECONNRESET
|
331
|
+
puts 'Connection reset by client'
|
332
|
+
ensure
|
333
|
+
timeout.stop
|
334
|
+
client.close
|
335
|
+
end
|
336
|
+
|
337
|
+
while (client = server.accept)
|
338
|
+
spin { handle_client(client) }
|
339
|
+
end
|
340
|
+
```
|
341
|
+
|
342
|
+
## What Else Can I Do with Polyphony?
|
343
|
+
|
344
|
+
Polyphony currently provides support for any library that uses Ruby's stock
|
345
|
+
`socket` and `openssl` classes. Polyphony also includes adapters for the `pg`,
|
346
|
+
`redis` and `irb` gems. It also includes an implementation of an integrated HTTP
|
347
|
+
1 / HTTP 2 / websockets web server with support for TLS termination, ALPN
|
348
|
+
protocol selection and preliminary rack support.
|
349
|
+
|
350
|
+
## Fibers are the Future!
|
351
|
+
|
352
|
+
Implementing concurrency at the level of fibers opens up so many new
|
353
|
+
possibilities for Ruby. Polyphony has the performance characteristics and
|
354
|
+
provides the necessary tools for transforming how concurrent Ruby apps are
|
355
|
+
written. Polyphony is still new, and the present documentation is far from being
|
356
|
+
complete. To learn more about Polyphony, read the [technical
|
357
|
+
overview](../../main-concepts/design-principles/). For more examples please
|
358
|
+
consult the [Github
|
359
|
+
repository](https://github.com/digital-fabric/polyphony/tree/master/examples).
|
data/docs/index.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Home
|
4
|
+
nav_order: 1
|
5
|
+
permalink: /
|
6
|
+
next_title: Installing Polyphony
|
7
|
+
---
|
8
|
+
|
9
|
+
<p align="center"><img src="{{ 'polyphony-logo.png' | absolute_url }}" /></p>
|
10
|
+
|
11
|
+
# Polyphony
|
12
|
+
{:.text-center .logo-title}
|
13
|
+
|
14
|
+
## Fine-grained concurrency for Ruby
|
15
|
+
{:.text-center .logo-title}
|
16
|
+
|
17
|
+
Polyphony is a library for building concurrent applications in Ruby. Polyphony
|
18
|
+
implements a comprehensive
|
19
|
+
[fiber](https://ruby-doc.org/core-2.5.1/Fiber.html)-based concurrency model,
|
20
|
+
using [libev](https://github.com/enki/libev) as a high-performance event reactor
|
21
|
+
for I/O, timers, and other asynchronous events.
|
22
|
+
|
23
|
+
[Overview](getting-started/overview){: .btn .btn-green .text-gamma }
|
24
|
+
[Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
|
25
|
+
[Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
|
26
|
+
{:.text-center .mt-6 .h-align-center }
|
27
|
+
|
28
|
+
## Focused on Developer Happiness
|
29
|
+
|
30
|
+
Polyphony is designed to make concurrent Ruby programming feel natural and
|
31
|
+
fluent. The Polyphony API is easy to use, easy to understand, and above all
|
32
|
+
idiomatic.
|
33
|
+
|
34
|
+
## Optimized for High Performance
|
35
|
+
|
36
|
+
Polyphony offers high performance for I/O bound Ruby apps. Distributing
|
37
|
+
concurrent operations over fibers, instead of threads or processes, minimizes
|
38
|
+
memory consumption and reduces the cost of context-switching.
|
39
|
+
|
40
|
+
## Designed for Interoperability
|
41
|
+
|
42
|
+
With Polyphony you can use any of the stock Ruby classes and modules like `IO`,
|
43
|
+
`Process`, `Socket` and `OpenSSL` in a concurrent multi-fiber environment. In
|
44
|
+
addition, Polyphony provides a structured model for exception handling that
|
45
|
+
builds on and enhances Ruby's exception handling system.
|
46
|
+
|
47
|
+
## A Growing Ecosystem
|
48
|
+
|
49
|
+
Polyphony includes a full-blown HTTP server implementation with integrated
|
50
|
+
support for HTTP 2, WebSockets, TLS/SSL termination and more. Polyphony also
|
51
|
+
provides fiber-aware adapters for connecting to PostgreSQL and Redis. More
|
52
|
+
adapters are being developed.
|
53
|
+
|
54
|
+
## Features
|
55
|
+
|
56
|
+
* Co-operative scheduling of concurrent tasks using Ruby fibers.
|
57
|
+
* High-performance event reactor for handling I/O events and timers.
|
58
|
+
* Natural, sequential programming style that makes it easy to reason about
|
59
|
+
concurrent code.
|
60
|
+
* Abstractions and constructs for controlling the execution of concurrent code:
|
61
|
+
supervisors, throttling, resource pools etc.
|
62
|
+
* Code can use native networking classes and libraries, growing support for
|
63
|
+
third-party gems such as `pg` and `redis`.
|
64
|
+
* Use stdlib classes such as `TCPServer` and `TCPSocket` and `Net::HTTP`.
|
65
|
+
* Competitive performance and scalability characteristics, in terms of both
|
66
|
+
throughput and memory consumption.
|
67
|
+
|
68
|
+
## Prior Art
|
69
|
+
|
70
|
+
Polyphony draws inspiration from the following, in no particular order:
|
71
|
+
|
72
|
+
* [nio4r](https://github.com/socketry/nio4r/) and
|
73
|
+
[async](https://github.com/socketry/async) (Polyphony's C-extension code
|
74
|
+
started as a spinoff of
|
75
|
+
[nio4r's](https://github.com/socketry/nio4r/tree/master/ext))
|
76
|
+
* The [go scheduler](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)
|
77
|
+
* [EventMachine](https://github.com/eventmachine/eventmachine)
|
78
|
+
* [Trio](https://trio.readthedocs.io/)
|
79
|
+
* [Erlang supervisors](http://erlang.org/doc/man/supervisor.html) (and actually,
|
80
|
+
Erlang in general)
|
81
|
+
|
82
|
+
## Developer Resources
|
83
|
+
|
84
|
+
* [Tutorial](getting-started/tutorial)
|
85
|
+
* [Main Concepts](main-concepts/concurrency/)
|
86
|
+
* [User Guide](user-guide/all-about-timers/)
|
87
|
+
* [API Reference](api-reference/exception/)
|
88
|
+
* [Examples](https://github.com/digital-fabric/polyphony/tree/9e0f3b09213156bdf376ef33684ef267517f06e8/examples/README.md)
|
89
|
+
|
90
|
+
## Contributing to Polyphony
|
91
|
+
|
92
|
+
Issues and pull requests will be gladly accepted. Please use the [Polyphony git
|
93
|
+
repository](https://github.com/digital-fabric/polyphony) as your primary point
|
94
|
+
of departure for contributing.
|