polyphony 0.13
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/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.
|