polyphony 0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +86 -0
- data/README.md +400 -0
- data/ext/ev/extconf.rb +19 -0
- data/lib/polyphony.rb +26 -0
- data/lib/polyphony/core.rb +45 -0
- data/lib/polyphony/core/async.rb +36 -0
- data/lib/polyphony/core/cancel_scope.rb +61 -0
- data/lib/polyphony/core/channel.rb +39 -0
- data/lib/polyphony/core/coroutine.rb +106 -0
- data/lib/polyphony/core/exceptions.rb +24 -0
- data/lib/polyphony/core/fiber_pool.rb +98 -0
- data/lib/polyphony/core/supervisor.rb +75 -0
- data/lib/polyphony/core/sync.rb +20 -0
- data/lib/polyphony/core/thread.rb +49 -0
- data/lib/polyphony/core/thread_pool.rb +58 -0
- data/lib/polyphony/core/throttler.rb +38 -0
- data/lib/polyphony/extensions/io.rb +62 -0
- data/lib/polyphony/extensions/kernel.rb +161 -0
- data/lib/polyphony/extensions/postgres.rb +96 -0
- data/lib/polyphony/extensions/redis.rb +68 -0
- data/lib/polyphony/extensions/socket.rb +85 -0
- data/lib/polyphony/extensions/ssl.rb +73 -0
- data/lib/polyphony/fs.rb +22 -0
- data/lib/polyphony/http/agent.rb +214 -0
- data/lib/polyphony/http/http1.rb +124 -0
- data/lib/polyphony/http/http1_request.rb +71 -0
- data/lib/polyphony/http/http2.rb +66 -0
- data/lib/polyphony/http/http2_request.rb +69 -0
- data/lib/polyphony/http/rack.rb +27 -0
- data/lib/polyphony/http/server.rb +43 -0
- data/lib/polyphony/line_reader.rb +82 -0
- data/lib/polyphony/net.rb +59 -0
- data/lib/polyphony/net_old.rb +299 -0
- data/lib/polyphony/resource_pool.rb +56 -0
- data/lib/polyphony/server_task.rb +18 -0
- data/lib/polyphony/testing.rb +34 -0
- data/lib/polyphony/version.rb +5 -0
- metadata +170 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f95ac584e1a686e3ff45ec50878bce9cc3ea0457f2894e0839e69c79621e3ab9
|
4
|
+
data.tar.gz: 189eb3effd86a54ab16eec64cd4159ce851fdbd249a6b227c02ecfedfd35c987
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e935934c596936a1fcce5ecf5f87cb288245755bd4e08f9dce6fde16cffd486134f66bc209774418b1942b00c13e015f6bfa6c55026e1233fe7a4533aa3411b6
|
7
|
+
data.tar.gz: a95b472caf17fa3c5a1f7afb8757e891e445a9afe7770131054a1db49c4a4529afa85acc723e1b621f4ae1200de4bfaef42a6a72b8002decbba4324be21ea3d5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
0.13 2019-01-05
|
2
|
+
---------------
|
3
|
+
|
4
|
+
* Rename Rubato to Polyphony (I know, this is getting silly...)
|
5
|
+
|
6
|
+
0.12 2019-01-01
|
7
|
+
---------------
|
8
|
+
|
9
|
+
* Add Coroutine#resume
|
10
|
+
* Improve startup time
|
11
|
+
* Accept rate: or interval: arguments for throttle
|
12
|
+
* Set correct backtrace for errors
|
13
|
+
* Improve handling of uncaught raised errors
|
14
|
+
* Implement HTTP 1.1/2 client agent with connection management
|
15
|
+
|
16
|
+
0.11 2018-12-27
|
17
|
+
---------------
|
18
|
+
|
19
|
+
* Move reactor loop to secondary fiber, allow blocking operations on main
|
20
|
+
fiber.
|
21
|
+
* Example implementation of erlang-style generic server pattern (implement
|
22
|
+
async API to a coroutine)
|
23
|
+
* Implement coroutine mailboxes, Coroutine#<<, Coroutine#receive, Kernel.receive
|
24
|
+
for message passing
|
25
|
+
* Add Coroutine.current for getting current coroutine
|
26
|
+
|
27
|
+
0.10 2018-11-20
|
28
|
+
---------------
|
29
|
+
|
30
|
+
* Rewrite Rubato core for simpler code and better performance
|
31
|
+
* Implement EV.snooze (sleep until next tick)
|
32
|
+
* Coroutine encapsulates a task spawned on a separate fiber
|
33
|
+
* Supervisor supervises multiple coroutines
|
34
|
+
* CancelScope used to cancel an ongoing task (usually with a timeout)
|
35
|
+
* Rate throttling
|
36
|
+
* Implement async SSL server
|
37
|
+
|
38
|
+
0.9 2018-11-14
|
39
|
+
--------------
|
40
|
+
|
41
|
+
* Rename Nuclear to Rubato
|
42
|
+
|
43
|
+
0.8 2018-10-04
|
44
|
+
--------------
|
45
|
+
|
46
|
+
* Replace nio4r with in-house extension based on libev, with better API,
|
47
|
+
better performance, support for IO, timer, signal and async watchers
|
48
|
+
* Fix mem leak coming from nio4r (probably related to code in Selector#select)
|
49
|
+
|
50
|
+
0.7 2018-09-13
|
51
|
+
--------------
|
52
|
+
|
53
|
+
* Implement resource pool
|
54
|
+
* transaction method for pg cient
|
55
|
+
* Async connect for pg client
|
56
|
+
* Add testing module for testing async code
|
57
|
+
* Improve HTTP server performance
|
58
|
+
* Proper promise chaining
|
59
|
+
|
60
|
+
0.6 2018-09-11
|
61
|
+
--------------
|
62
|
+
|
63
|
+
* Add http, redis, pg dependencies
|
64
|
+
* Move ALPN code inside net module
|
65
|
+
|
66
|
+
0.4 2018-09-10
|
67
|
+
--------------
|
68
|
+
|
69
|
+
* Code refactored and reogranized
|
70
|
+
* Fix recursion in next_tick
|
71
|
+
* HTTP 2 server with support for ALPN protocol negotiation and HTTP upgrade
|
72
|
+
* OpenSSL server
|
73
|
+
|
74
|
+
0.3 2018-09-06
|
75
|
+
--------------
|
76
|
+
|
77
|
+
* Event reactor
|
78
|
+
* Timers
|
79
|
+
* Promises
|
80
|
+
* async/await syntax for promises
|
81
|
+
* IO and read/write stream
|
82
|
+
* TCP server/client
|
83
|
+
* Promised threads
|
84
|
+
* HTTP server
|
85
|
+
* Redis interface
|
86
|
+
* PostgreSQL interface
|
data/README.md
ADDED
@@ -0,0 +1,400 @@
|
|
1
|
+
# Polyphony - Fiber-Based Concurrency for Ruby
|
2
|
+
|
3
|
+
[INSTALL](#installing-polyphony) |
|
4
|
+
[TUTORIAL](#getting-started) |
|
5
|
+
[EXAMPLES](examples) |
|
6
|
+
[TEHNICAL OVERVIEW](#how-polyphony-works---a-technical-overview) |
|
7
|
+
[REFERENCE](#api-reference) |
|
8
|
+
[EXTENDING](#extending-polyphony)
|
9
|
+
|
10
|
+
> Polyphony | pəˈlɪf(ə)ni | *Music* - the style of simultaneously combining a
|
11
|
+
> number of parts, each forming an individual melody and harmonizing with each
|
12
|
+
> other.
|
13
|
+
|
14
|
+
**Note**: Polyphony is designed to work with recent versions of Ruby and
|
15
|
+
supports Linux and MacOS only. This software is currently at the alpha stage.
|
16
|
+
|
17
|
+
## What is Polyphony
|
18
|
+
|
19
|
+
Polyphony is a library for building concurrent applications in Ruby. Polyphony
|
20
|
+
harnesses the power of
|
21
|
+
[Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html) to provide a
|
22
|
+
cooperative, sequential coroutine-based concurrency model. Under the hood,
|
23
|
+
Polyphony uses [libev](https://github.com/enki/libev) as a high-performance event
|
24
|
+
reactor that provides timers, I/O watchers and other asynchronous event
|
25
|
+
primitives.
|
26
|
+
|
27
|
+
Polyphony makes it possible to use normal Ruby built-in classes like `IO`, and
|
28
|
+
`Socket` in a concurrent fashion without having to resort to threads. Polyphony
|
29
|
+
takes care of context-switching automatically whenever a blocking call like
|
30
|
+
`Socket#accept` or `IO#read` is issued.
|
31
|
+
|
32
|
+
## Features
|
33
|
+
|
34
|
+
- Co-operative scheduling of concurrent tasks using Ruby fibers.
|
35
|
+
- High-performance event reactor for handling I/O events and timers.
|
36
|
+
- Natural, sequential programming style that makes it easy to reason about
|
37
|
+
concurrent code.
|
38
|
+
- Higher-order constructs for controlling the execution of concurrent code:
|
39
|
+
coprocesses, supervisors, cancel scopes, throttling, resource pools etc.
|
40
|
+
- Code can use native networking classes and libraries, growing support for
|
41
|
+
third-party gems such as `pg` and `redis`.
|
42
|
+
- Comprehensive HTTP 1.0 / 1.1 / 2 client and server APIs.
|
43
|
+
- Excellent performance and scalability characteristics, in terms of both
|
44
|
+
throughput and memory consumption.
|
45
|
+
|
46
|
+
## Prior Art
|
47
|
+
|
48
|
+
Polyphony draws inspiration from the following, in no particular order:
|
49
|
+
|
50
|
+
* [nio4r](https://github.com/socketry/nio4r/) and [async](https://github.com/socketry/async)
|
51
|
+
* [EventMachine](https://github.com/eventmachine/eventmachine)
|
52
|
+
* [Trio](https://trio.readthedocs.io/)
|
53
|
+
* [Erlang supervisors](http://erlang.org/doc/man/supervisor.html) (and actually,
|
54
|
+
Erlang in general)
|
55
|
+
|
56
|
+
## Installing Polyphony
|
57
|
+
|
58
|
+
```bash
|
59
|
+
$ gem install polyphony
|
60
|
+
```
|
61
|
+
|
62
|
+
## Getting Started
|
63
|
+
|
64
|
+
Polyphony is designed to help you write high-performance, concurrent code in
|
65
|
+
Ruby. It does so by turning every call which might block, such as `sleep` or
|
66
|
+
`read` into a concurrent operation, which yields control to an event reactor.
|
67
|
+
The reactor, in turn, may schedule other operations once they can be resumed. In
|
68
|
+
that manner, multiple ongoing operations may be processed concurrently.
|
69
|
+
|
70
|
+
There are multiple ways to start a concurrent operation, the most common of
|
71
|
+
which is `Kernel#spawn`:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
require 'polyphony'
|
75
|
+
|
76
|
+
spawn do
|
77
|
+
puts "A going to sleep"
|
78
|
+
sleep 1
|
79
|
+
puts "A woken up"
|
80
|
+
end
|
81
|
+
|
82
|
+
spawn do
|
83
|
+
puts "B going to sleep"
|
84
|
+
sleep 1
|
85
|
+
puts "B woken up"
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
In the above example, both `sleep` calls will be executed concurrently, and thus
|
90
|
+
the program will take approximately only 1 second to execute. Note the lack of
|
91
|
+
any boilerplate relating to concurrency. Each `spawn` block starts a
|
92
|
+
*coroutine*, and is executed in sequential manner.
|
93
|
+
|
94
|
+
> **Coroutines - the basic unit of concurrency**: In Polyphony, concurrent
|
95
|
+
> operations take place inside coroutines. A `Coroutine` is executed on top of a
|
96
|
+
> `Fiber`, which allows it to be suspended whenever a blocking operation is
|
97
|
+
> called, and resumed once that operation has been completed. Coroutines offer
|
98
|
+
> significant advantages over threads - they consume only about 10KB, switching
|
99
|
+
> between them is much faster than switching threads, and literally millions of
|
100
|
+
> them can be spawned without affecting performance*. Besides, Ruby does not yet
|
101
|
+
> allow parallel execution of threads.
|
102
|
+
>
|
103
|
+
> \* *This is a totally unsubstantiated claim which has not been proved in
|
104
|
+
> practice*.
|
105
|
+
|
106
|
+
## An echo server in Polyphony
|
107
|
+
|
108
|
+
To take matters further, let's see how networking can be done using Polyphony.
|
109
|
+
Here's a bare-bones echo server written using Polyphony:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
require 'polyphony'
|
113
|
+
|
114
|
+
server = TCPServer.open(1234)
|
115
|
+
while client = server.accept
|
116
|
+
# spawn starts a new coroutine on a separate fiber
|
117
|
+
spawn {
|
118
|
+
while data = client.read rescue nil
|
119
|
+
client.write(data)
|
120
|
+
end
|
121
|
+
}
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
This example demonstrates several features of Polyphony:
|
126
|
+
|
127
|
+
- The code uses the native `TCPServer` class from Ruby's stdlib, to setup a TCP
|
128
|
+
server. The result of `server.accept` is also a native `TCPSocket` object.
|
129
|
+
There are no wrapper classes being used.
|
130
|
+
- The only hint of the code being concurrent is the use of `Kernel#spawn`,
|
131
|
+
which starts a new coroutine on a dedicated fiber. This allows serving
|
132
|
+
multiple clients at once. Whenever a blocking call is issued, such as
|
133
|
+
`#accept` or `#read`, execution is *yielded* to the event loop, which will
|
134
|
+
resume only those coroutines which are ready to be resumed.
|
135
|
+
- Exception handling is done using the normal Ruby constructs `raise`, `rescue`
|
136
|
+
and `ensure`. Exceptions never go unhandled (as might be the case with Ruby
|
137
|
+
threads), and must be dealt with explicitly. An unhandled exception will cause
|
138
|
+
the Ruby process to exit.
|
139
|
+
|
140
|
+
## Going further
|
141
|
+
|
142
|
+
To learn more about using Polyphony to build concurrent applications, read the
|
143
|
+
technical overview below, or look at the [included examples](examples). A
|
144
|
+
thorough reference is forthcoming.
|
145
|
+
|
146
|
+
## How Polyphony Works - a Technical Overview
|
147
|
+
|
148
|
+
### Fiber-based concurrency
|
149
|
+
|
150
|
+
The built-in `Fiber` class provides a very elegant, if low-level, foundation for
|
151
|
+
implementing cooperative, light-weight concurrency (it can also be used for other stuff like generators). Fiber or continuation-based concurrency can be
|
152
|
+
considered as the
|
153
|
+
[*third way*](https://en.wikipedia.org/wiki/Fiber_(computer_science))
|
154
|
+
of writing concurrent programs (the other two being multi-process concurrency
|
155
|
+
and multi-thread concurrency), and can provide very good performance
|
156
|
+
characteristics for I/O-bound applications.
|
157
|
+
|
158
|
+
In contrast to callback-based concurrency (e.g. Node.js or EventMachine), fibers
|
159
|
+
allow writing concurrent code in a sequential manner without having to split
|
160
|
+
your logic into different locations, or submitting to
|
161
|
+
[callback hell](http://callbackhell.com/).
|
162
|
+
|
163
|
+
Polyphony builds on the foundation of Ruby fibers in order to facilitate writing
|
164
|
+
high-performance I/O-bound applications in Ruby.
|
165
|
+
|
166
|
+
### Context-switching on blocking calls
|
167
|
+
|
168
|
+
Ruby monkey-patches existing methods such as `sleep` or `IO#read` to setup an
|
169
|
+
IO watcher and suspend the current fiber until the IO object is ready to be
|
170
|
+
read. Once the IO watcher is signalled, the associated fiber is resumed and the
|
171
|
+
method call can continue. Here's a simplified implementation of
|
172
|
+
[`IO#read`](lib/polyphony/io.rb#24-36):
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class IO
|
176
|
+
def read(max = 8192)
|
177
|
+
loop do
|
178
|
+
result = read_nonblock(max, exception: false)
|
179
|
+
case result
|
180
|
+
when nil then raise IOError
|
181
|
+
when :wait_readable then read_watcher.await
|
182
|
+
else return result
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
The magic starts in [`IOWatcher#await`](ext/ev/io.c#157-179), where the watcher
|
190
|
+
is started and the current fiber is suspended (it "yields" in Ruby parlance).
|
191
|
+
Here's a naïve implementation (the actual implementation is written in C):
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class IOWatcher
|
195
|
+
def await
|
196
|
+
@fiber = Fiber.current
|
197
|
+
start
|
198
|
+
yield_to_reactor_fiber
|
199
|
+
end
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
> **Running a high-performance event loop**: Polyphony runs a libev-based event
|
204
|
+
> loop that watches events such as IO-readiness, elapsed timers, received
|
205
|
+
> signals and other asynchronous happenings, and uses them to control fiber
|
206
|
+
> execution. The event loop itself is run on a separate fiber, allowing the main
|
207
|
+
> fiber as well to perform blocking operations.
|
208
|
+
|
209
|
+
When the IO watcher is [signalled](ext/ev/io.c#99-116): the fiber associated
|
210
|
+
with the watcher is resumed, and control is given back to the calling method.
|
211
|
+
Here's a naïve implementation:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
class IOWatcher
|
215
|
+
def signal
|
216
|
+
@fiber.transfer
|
217
|
+
end
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
### Additional concurrency constructs
|
222
|
+
|
223
|
+
In order to facilitate writing concurrent code, Polyphony provides additional
|
224
|
+
constructs that make it easier to spawn concurrent tasks and to control them.
|
225
|
+
|
226
|
+
`CancelScope` - an abstraction used to cancel the execution of one or more
|
227
|
+
coroutines or supervisors. It usually works by defining a timeout for the
|
228
|
+
completion of a task. Any blocking operation can be cancelled, including
|
229
|
+
a coroutine or a supervisor. The developer may choose to cancel with or without
|
230
|
+
an exception with `cancel` or `move_on`, respectively. Cancel scopes are
|
231
|
+
typically started using `Kernel.cancel_after` and `Kernel.move_on`:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
def echoer(client)
|
235
|
+
# cancel after 10 seconds if inactivity
|
236
|
+
move_on_after(10) { |scope|
|
237
|
+
loop {
|
238
|
+
data = client.read
|
239
|
+
scope.reset_timeout
|
240
|
+
client.write
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
```
|
245
|
+
|
246
|
+
`ResourcePool` - a class used to control access to shared resources. It can be
|
247
|
+
used to control concurrent access to database connections, or to limit
|
248
|
+
concurrent requests to an external API:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
# up to 5 concurrent connections
|
252
|
+
Pool = Polyphony::ResourcePool.new(limit: 5) {
|
253
|
+
# the block sets up the resource
|
254
|
+
PG.connect(...)
|
255
|
+
}
|
256
|
+
|
257
|
+
1000.times {
|
258
|
+
spawn {
|
259
|
+
Pool.acquire { |db| p db.query('select 1') }
|
260
|
+
}
|
261
|
+
}
|
262
|
+
```
|
263
|
+
|
264
|
+
`Supervisor` - a class used to control one or more `Coroutine`s. It can be used
|
265
|
+
to start, stop and restart multiple coroutines. A supervisor can also be
|
266
|
+
used for awaiting the completion of multiple coroutines. It is usually started
|
267
|
+
using `Kernel.supervise`:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
supervise { |s|
|
271
|
+
s.spawn { sleep 1 }
|
272
|
+
s.spawn { sleep 2 }
|
273
|
+
s.spawn { sleep 3 }
|
274
|
+
}
|
275
|
+
puts "done sleeping"
|
276
|
+
```
|
277
|
+
|
278
|
+
`ThreadPool` - a pool of threads used to run any operation that cannot be
|
279
|
+
implemented using non-blocking calls, such as file system calls. The operation
|
280
|
+
is offloaded to a worker thread, allowing the event loop to continue processing
|
281
|
+
other tasks. For example, `IO.read` and `File.stat` are both reimplemented
|
282
|
+
using the Polyphony thread pool. You can easily use the thread pool to run your
|
283
|
+
own blocking operations as follows:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
result = Polyphony::ThreadPool.process { long_running_process }
|
287
|
+
```
|
288
|
+
|
289
|
+
`Throttler` - a mechanism for throttling an arbitrary task, such as sending of
|
290
|
+
emails, or crawling a website. A throttler is normally created using
|
291
|
+
`Kernel.throttle`, and can even be used to throttle operations across multiple
|
292
|
+
coroutines:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
server = Net.tcp_listen(1234)
|
296
|
+
throttler = throttle(rate: 10) # up to 10 times per second
|
297
|
+
|
298
|
+
while client = server.accept
|
299
|
+
spawn {
|
300
|
+
throttler.call {
|
301
|
+
while data = client.read
|
302
|
+
client.write(data)
|
303
|
+
end
|
304
|
+
}
|
305
|
+
}
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
## API Reference
|
310
|
+
|
311
|
+
To be continued...
|
312
|
+
|
313
|
+
## Extending Polyphony
|
314
|
+
|
315
|
+
Polyphony was designed to ease the transition from blocking APIs and
|
316
|
+
callback-based API to non-blocking, fiber-based ones. It is important to
|
317
|
+
understand that not all blocking calls can be easily converted into
|
318
|
+
non-blocking calls. That might be the case with Ruby gems based on C-extensions,
|
319
|
+
such as database libraries. In that case, Polyphony's built-in
|
320
|
+
[thread pool](#threadpool) might be used for offloading such blocking calls.
|
321
|
+
|
322
|
+
### Adapting callback-based APIs
|
323
|
+
|
324
|
+
Some of the most common patterns in Ruby APIs is the callback pattern, in which
|
325
|
+
the API takes a block as a callback to be called upon completion of a task. One
|
326
|
+
such example can be found in the excellent
|
327
|
+
[http_parser.rb](https://github.com/tmm1/http_parser.rb/) gem, which is used by
|
328
|
+
Polyphony itself to provide HTTP 1 functionality. The `HTTP:Parser` provides
|
329
|
+
multiple hooks, or callbacks, for being notified when an HTTP request is
|
330
|
+
complete. The typical callback-based setup is as follows:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
require 'http/parser'
|
334
|
+
@parser = Http::Parser.new
|
335
|
+
|
336
|
+
def on_receive(data)
|
337
|
+
@parser < data
|
338
|
+
end
|
339
|
+
|
340
|
+
@parser.on_message_complete do |env|
|
341
|
+
process_request(env)
|
342
|
+
end
|
343
|
+
```
|
344
|
+
|
345
|
+
A program using `http_parser.rb` in conjunction with Polyphony might do the
|
346
|
+
following:
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
require 'http/parser'
|
350
|
+
require 'modulation'
|
351
|
+
|
352
|
+
def handle_client(client)
|
353
|
+
parser = Http::Parser.new
|
354
|
+
req = nil
|
355
|
+
parser.on_message_complete { |env| req = env }
|
356
|
+
loop do
|
357
|
+
parser << client.read
|
358
|
+
if req
|
359
|
+
handle_request(req)
|
360
|
+
req = nil
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
Another possibility would be to monkey-patch `Http::Parser` in order to
|
367
|
+
encapsulate the state of the request:
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
class Http::Parser
|
371
|
+
def setup
|
372
|
+
self.on_message_complete = proc { @request_complete = true }
|
373
|
+
end
|
374
|
+
|
375
|
+
def parser(data)
|
376
|
+
self << data
|
377
|
+
return nil unless @request_complete
|
378
|
+
|
379
|
+
@request_complete = nil
|
380
|
+
self
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def handle_client(client)
|
385
|
+
parser = Http::Parser.new
|
386
|
+
loop do
|
387
|
+
if req == parser.parse(client.read)
|
388
|
+
handle_request(req)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
```
|
393
|
+
|
394
|
+
### Contributing to Polyphony
|
395
|
+
|
396
|
+
If there's some blocking behavior you'd like to see handled by Polyphony, please
|
397
|
+
let us know by
|
398
|
+
[creating an issue](https://github.com/digital-fabric/polyphony/issues). Our aim
|
399
|
+
is for Polyphony to be a comprehensive solution for writing concurrent Ruby
|
400
|
+
programs.
|