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.
Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/.gitbook.yaml +4 -0
  3. data/.github/workflows/test.yml +29 -0
  4. data/.gitignore +59 -0
  5. data/.rubocop.yml +175 -0
  6. data/CHANGELOG.md +393 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +141 -0
  9. data/LICENSE +21 -0
  10. data/README.md +51 -0
  11. data/Rakefile +26 -0
  12. data/TODO.md +201 -0
  13. data/bin/polyphony-debug +87 -0
  14. data/docs/_config.yml +64 -0
  15. data/docs/_includes/head.html +40 -0
  16. data/docs/_includes/title.html +1 -0
  17. data/docs/_sass/custom/custom.scss +10 -0
  18. data/docs/_sass/overrides.scss +0 -0
  19. data/docs/_user-guide/all-about-timers.md +126 -0
  20. data/docs/_user-guide/index.md +9 -0
  21. data/docs/_user-guide/web-server.md +136 -0
  22. data/docs/api-reference/exception.md +27 -0
  23. data/docs/api-reference/fiber.md +425 -0
  24. data/docs/api-reference/index.md +9 -0
  25. data/docs/api-reference/io.md +36 -0
  26. data/docs/api-reference/object.md +99 -0
  27. data/docs/api-reference/polyphony-baseexception.md +33 -0
  28. data/docs/api-reference/polyphony-cancel.md +26 -0
  29. data/docs/api-reference/polyphony-moveon.md +24 -0
  30. data/docs/api-reference/polyphony-net.md +20 -0
  31. data/docs/api-reference/polyphony-process.md +28 -0
  32. data/docs/api-reference/polyphony-resourcepool.md +59 -0
  33. data/docs/api-reference/polyphony-restart.md +18 -0
  34. data/docs/api-reference/polyphony-terminate.md +18 -0
  35. data/docs/api-reference/polyphony-threadpool.md +67 -0
  36. data/docs/api-reference/polyphony-throttler.md +77 -0
  37. data/docs/api-reference/polyphony.md +36 -0
  38. data/docs/api-reference/thread.md +88 -0
  39. data/docs/assets/img/echo-fibers.svg +1 -0
  40. data/docs/assets/img/sleeping-fiber.svg +1 -0
  41. data/docs/faq.md +195 -0
  42. data/docs/favicon.ico +0 -0
  43. data/docs/getting-started/index.md +10 -0
  44. data/docs/getting-started/installing.md +34 -0
  45. data/docs/getting-started/overview.md +486 -0
  46. data/docs/getting-started/tutorial.md +359 -0
  47. data/docs/index.md +94 -0
  48. data/docs/main-concepts/concurrency.md +151 -0
  49. data/docs/main-concepts/design-principles.md +161 -0
  50. data/docs/main-concepts/exception-handling.md +291 -0
  51. data/docs/main-concepts/extending.md +89 -0
  52. data/docs/main-concepts/fiber-scheduling.md +197 -0
  53. data/docs/main-concepts/index.md +9 -0
  54. data/docs/polyphony-logo.png +0 -0
  55. data/examples/adapters/concurrent-ruby.rb +9 -0
  56. data/examples/adapters/pg_client.rb +36 -0
  57. data/examples/adapters/pg_notify.rb +35 -0
  58. data/examples/adapters/pg_pool.rb +43 -0
  59. data/examples/adapters/pg_transaction.rb +31 -0
  60. data/examples/adapters/redis_blpop.rb +12 -0
  61. data/examples/adapters/redis_channels.rb +122 -0
  62. data/examples/adapters/redis_client.rb +19 -0
  63. data/examples/adapters/redis_pubsub.rb +26 -0
  64. data/examples/adapters/redis_pubsub_perf.rb +68 -0
  65. data/examples/core/01-spinning-up-fibers.rb +18 -0
  66. data/examples/core/02-awaiting-fibers.rb +20 -0
  67. data/examples/core/03-interrupting.rb +39 -0
  68. data/examples/core/04-handling-signals.rb +19 -0
  69. data/examples/core/xx-agent.rb +102 -0
  70. data/examples/core/xx-at_exit.rb +29 -0
  71. data/examples/core/xx-caller.rb +12 -0
  72. data/examples/core/xx-channels.rb +45 -0
  73. data/examples/core/xx-daemon.rb +14 -0
  74. data/examples/core/xx-deadlock.rb +8 -0
  75. data/examples/core/xx-deferring-an-operation.rb +14 -0
  76. data/examples/core/xx-erlang-style-genserver.rb +81 -0
  77. data/examples/core/xx-exception-backtrace.rb +40 -0
  78. data/examples/core/xx-fork-cleanup.rb +22 -0
  79. data/examples/core/xx-fork-spin.rb +42 -0
  80. data/examples/core/xx-fork-terminate.rb +27 -0
  81. data/examples/core/xx-forking.rb +24 -0
  82. data/examples/core/xx-move_on.rb +23 -0
  83. data/examples/core/xx-pingpong.rb +18 -0
  84. data/examples/core/xx-queue-async.rb +120 -0
  85. data/examples/core/xx-readpartial.rb +18 -0
  86. data/examples/core/xx-recurrent-timer.rb +12 -0
  87. data/examples/core/xx-resource_delegate.rb +31 -0
  88. data/examples/core/xx-signals.rb +16 -0
  89. data/examples/core/xx-sleep-forever.rb +9 -0
  90. data/examples/core/xx-sleeping.rb +25 -0
  91. data/examples/core/xx-snooze-starve.rb +16 -0
  92. data/examples/core/xx-spin-fork.rb +49 -0
  93. data/examples/core/xx-spin_error_backtrace.rb +33 -0
  94. data/examples/core/xx-state-machine.rb +51 -0
  95. data/examples/core/xx-stop.rb +20 -0
  96. data/examples/core/xx-supervise-process.rb +30 -0
  97. data/examples/core/xx-supervisors.rb +21 -0
  98. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  99. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  100. data/examples/core/xx-thread-sleep.rb +17 -0
  101. data/examples/core/xx-thread-snooze.rb +34 -0
  102. data/examples/core/xx-thread_pool.rb +17 -0
  103. data/examples/core/xx-throttling.rb +18 -0
  104. data/examples/core/xx-timeout.rb +10 -0
  105. data/examples/core/xx-timer-gc.rb +17 -0
  106. data/examples/core/xx-trace.rb +79 -0
  107. data/examples/core/xx-using-a-mutex.rb +21 -0
  108. data/examples/core/xx-worker-thread.rb +30 -0
  109. data/examples/io/tunnel.rb +48 -0
  110. data/examples/io/xx-backticks.rb +11 -0
  111. data/examples/io/xx-echo_client.rb +25 -0
  112. data/examples/io/xx-echo_client_from_stdin.rb +21 -0
  113. data/examples/io/xx-echo_pipe.rb +16 -0
  114. data/examples/io/xx-echo_server.rb +17 -0
  115. data/examples/io/xx-echo_server_with_timeout.rb +34 -0
  116. data/examples/io/xx-echo_stdin.rb +14 -0
  117. data/examples/io/xx-happy-eyeballs.rb +36 -0
  118. data/examples/io/xx-httparty.rb +38 -0
  119. data/examples/io/xx-irb.rb +17 -0
  120. data/examples/io/xx-net-http.rb +15 -0
  121. data/examples/io/xx-open.rb +16 -0
  122. data/examples/io/xx-switch.rb +15 -0
  123. data/examples/io/xx-system.rb +11 -0
  124. data/examples/io/xx-tcpserver.rb +15 -0
  125. data/examples/io/xx-tcpsocket.rb +18 -0
  126. data/examples/io/xx-zip.rb +19 -0
  127. data/examples/performance/fiber_transfer.rb +47 -0
  128. data/examples/performance/fs_read.rb +38 -0
  129. data/examples/performance/mem-usage.rb +56 -0
  130. data/examples/performance/messaging.rb +29 -0
  131. data/examples/performance/multi_snooze.rb +33 -0
  132. data/examples/performance/snooze.rb +39 -0
  133. data/examples/performance/snooze_raw.rb +39 -0
  134. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
  135. data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
  136. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  137. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  138. data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
  139. data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
  140. data/examples/performance/thread_pool_perf.rb +63 -0
  141. data/examples/performance/xx-array.rb +11 -0
  142. data/examples/performance/xx-fiber-switch.rb +9 -0
  143. data/examples/performance/xx-snooze.rb +15 -0
  144. data/examples/xx-spin.rb +32 -0
  145. data/ext/libev/Changes +548 -0
  146. data/ext/libev/LICENSE +37 -0
  147. data/ext/libev/README +59 -0
  148. data/ext/libev/README.embed +3 -0
  149. data/ext/libev/ev.c +5279 -0
  150. data/ext/libev/ev.h +856 -0
  151. data/ext/libev/ev_epoll.c +296 -0
  152. data/ext/libev/ev_kqueue.c +224 -0
  153. data/ext/libev/ev_linuxaio.c +642 -0
  154. data/ext/libev/ev_poll.c +156 -0
  155. data/ext/libev/ev_port.c +192 -0
  156. data/ext/libev/ev_select.c +316 -0
  157. data/ext/libev/ev_vars.h +215 -0
  158. data/ext/libev/ev_win32.c +162 -0
  159. data/ext/libev/ev_wrap.h +216 -0
  160. data/ext/libev/test_libev_win32.c +123 -0
  161. data/ext/polyphony/extconf.rb +20 -0
  162. data/ext/polyphony/fiber.c +109 -0
  163. data/ext/polyphony/libev.c +2 -0
  164. data/ext/polyphony/libev.h +9 -0
  165. data/ext/polyphony/libev_agent.c +882 -0
  166. data/ext/polyphony/polyphony.c +71 -0
  167. data/ext/polyphony/polyphony.h +97 -0
  168. data/ext/polyphony/polyphony_ext.c +21 -0
  169. data/ext/polyphony/queue.c +168 -0
  170. data/ext/polyphony/ring_buffer.c +96 -0
  171. data/ext/polyphony/ring_buffer.h +28 -0
  172. data/ext/polyphony/thread.c +208 -0
  173. data/ext/polyphony/tracing.c +11 -0
  174. data/lib/polyphony.rb +136 -0
  175. data/lib/polyphony/adapters/fs.rb +19 -0
  176. data/lib/polyphony/adapters/irb.rb +52 -0
  177. data/lib/polyphony/adapters/postgres.rb +110 -0
  178. data/lib/polyphony/adapters/process.rb +33 -0
  179. data/lib/polyphony/adapters/redis.rb +67 -0
  180. data/lib/polyphony/adapters/trace.rb +138 -0
  181. data/lib/polyphony/core/channel.rb +46 -0
  182. data/lib/polyphony/core/exceptions.rb +36 -0
  183. data/lib/polyphony/core/global_api.rb +124 -0
  184. data/lib/polyphony/core/resource_pool.rb +117 -0
  185. data/lib/polyphony/core/sync.rb +21 -0
  186. data/lib/polyphony/core/thread_pool.rb +64 -0
  187. data/lib/polyphony/core/throttler.rb +41 -0
  188. data/lib/polyphony/event.rb +17 -0
  189. data/lib/polyphony/extensions/core.rb +174 -0
  190. data/lib/polyphony/extensions/fiber.rb +379 -0
  191. data/lib/polyphony/extensions/io.rb +221 -0
  192. data/lib/polyphony/extensions/openssl.rb +81 -0
  193. data/lib/polyphony/extensions/socket.rb +150 -0
  194. data/lib/polyphony/extensions/thread.rb +108 -0
  195. data/lib/polyphony/net.rb +77 -0
  196. data/lib/polyphony/version.rb +5 -0
  197. data/polyphony.gemspec +40 -0
  198. data/test/coverage.rb +54 -0
  199. data/test/eg.rb +27 -0
  200. data/test/helper.rb +56 -0
  201. data/test/q.rb +24 -0
  202. data/test/run.rb +5 -0
  203. data/test/stress.rb +25 -0
  204. data/test/test_agent.rb +130 -0
  205. data/test/test_event.rb +59 -0
  206. data/test/test_ext.rb +196 -0
  207. data/test/test_fiber.rb +988 -0
  208. data/test/test_global_api.rb +352 -0
  209. data/test/test_io.rb +249 -0
  210. data/test/test_kernel.rb +57 -0
  211. data/test/test_process_supervision.rb +46 -0
  212. data/test/test_queue.rb +112 -0
  213. data/test/test_resource_pool.rb +138 -0
  214. data/test/test_signal.rb +100 -0
  215. data/test/test_socket.rb +34 -0
  216. data/test/test_supervise.rb +103 -0
  217. data/test/test_thread.rb +170 -0
  218. data/test/test_thread_pool.rb +101 -0
  219. data/test/test_throttler.rb +50 -0
  220. data/test/test_trace.rb +68 -0
  221. 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).
@@ -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.