polyphony 0.44.0 → 0.45.0
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 +4 -4
- data/.rubocop.yml +7 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +9 -11
- data/Rakefile +1 -1
- data/TODO.md +12 -7
- data/docs/_posts/2020-07-26-polyphony-0.44.md +77 -0
- data/docs/api-reference/thread.md +1 -1
- data/docs/getting-started/overview.md +14 -14
- data/docs/getting-started/tutorial.md +1 -1
- data/examples/core/{xx-agent.rb → xx-backend.rb} +5 -5
- data/examples/io/xx-pry.rb +18 -0
- data/examples/io/xx-rack_server.rb +71 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
- data/ext/polyphony/backend.h +41 -0
- data/ext/polyphony/event.c +3 -3
- data/ext/polyphony/extconf.rb +1 -1
- data/ext/polyphony/{libev_agent.c → libev_backend.c} +175 -175
- data/ext/polyphony/polyphony.c +1 -1
- data/ext/polyphony/polyphony.h +4 -4
- data/ext/polyphony/polyphony_ext.c +2 -2
- data/ext/polyphony/queue.c +2 -2
- data/ext/polyphony/thread.c +21 -21
- data/lib/polyphony.rb +13 -12
- data/lib/polyphony/adapters/irb.rb +2 -17
- data/lib/polyphony/adapters/mysql2.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +5 -5
- data/lib/polyphony/adapters/process.rb +2 -2
- data/lib/polyphony/adapters/readline.rb +17 -0
- data/lib/polyphony/adapters/sequel.rb +1 -1
- data/lib/polyphony/core/global_api.rb +11 -6
- data/lib/polyphony/core/resource_pool.rb +2 -2
- data/lib/polyphony/core/sync.rb +38 -2
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/extensions/core.rb +31 -20
- data/lib/polyphony/extensions/fiber.rb +1 -1
- data/lib/polyphony/extensions/io.rb +7 -8
- data/lib/polyphony/extensions/openssl.rb +6 -6
- data/lib/polyphony/extensions/socket.rb +4 -14
- data/lib/polyphony/extensions/thread.rb +6 -5
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +4 -3
- data/test/helper.rb +1 -1
- data/test/{test_agent.rb → test_backend.rb} +22 -22
- data/test/test_fiber.rb +4 -4
- data/test/test_io.rb +1 -1
- data/test/test_kernel.rb +5 -0
- data/test/test_signal.rb +3 -3
- data/test/test_sync.rb +52 -0
- metadata +40 -30
- data/.gitbook.yaml +0 -4
- data/ext/polyphony/agent.h +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9c8ab74213c6cc5e3852f73ee027ee496f2b04c6844e09856c9771ea5e7839a
|
4
|
+
data.tar.gz: 83d7c533024b6d6d633b9e18abc392911adfc07f728af826bd84cce35cafb20c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c42e49ebcd6fb10b384438cb7688801a387963bc2a6fb2c1d3de6d022a7731a7d88db138a34b364b35c9d71830b975a69cb1f14fb277166a9db1802e24e82dec
|
7
|
+
data.tar.gz: 6c578eacded00dd7a77a35124c69ebc966e4f9aa42264913df82af147f43955a2ecd86087630c085fcd4d6e964aa705202c5aa24bb4032648d3ba278d60d4f63
|
data/.rubocop.yml
CHANGED
@@ -81,6 +81,7 @@ Lint/SuppressedException:
|
|
81
81
|
- examples/**/*.rb
|
82
82
|
|
83
83
|
Metrics/MethodLength:
|
84
|
+
Max: 12
|
84
85
|
Exclude:
|
85
86
|
- lib/polyphony/http/server/rack.rb
|
86
87
|
- lib/polyphony/extensions/io.rb
|
@@ -111,6 +112,7 @@ Style/Documentation:
|
|
111
112
|
Exclude:
|
112
113
|
- test/**/*.rb
|
113
114
|
- examples/**/*.rb
|
115
|
+
- lib/polyphony/adapters/**/*.rb
|
114
116
|
|
115
117
|
Style/FormatString:
|
116
118
|
Exclude:
|
@@ -172,4 +174,8 @@ Style/RedundantRegexpEscape:
|
|
172
174
|
Enabled: true
|
173
175
|
|
174
176
|
Style/SlicingWithRange:
|
175
|
-
Enabled: true
|
177
|
+
Enabled: true
|
178
|
+
|
179
|
+
Style/RaiseArgs:
|
180
|
+
Exclude:
|
181
|
+
- lib/polyphony/extensions/fiber.rb
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
polyphony (0.
|
4
|
+
polyphony (0.45.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -11,6 +11,7 @@ GEM
|
|
11
11
|
ansi (1.5.0)
|
12
12
|
ast (2.4.0)
|
13
13
|
builder (3.2.4)
|
14
|
+
coderay (1.1.3)
|
14
15
|
colorator (1.1.0)
|
15
16
|
concurrent-ruby (1.1.6)
|
16
17
|
docile (1.3.2)
|
@@ -22,9 +23,6 @@ GEM
|
|
22
23
|
forwardable-extended (2.6.0)
|
23
24
|
hiredis (0.6.3)
|
24
25
|
http_parser.rb (0.6.0)
|
25
|
-
httparty (0.17.0)
|
26
|
-
mime-types (~> 3.0)
|
27
|
-
multi_xml (>= 0.5.2)
|
28
26
|
i18n (0.9.5)
|
29
27
|
concurrent-ruby (~> 1.0)
|
30
28
|
jekyll (3.8.6)
|
@@ -60,18 +58,14 @@ GEM
|
|
60
58
|
listen (3.2.1)
|
61
59
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
62
60
|
rb-inotify (~> 0.9, >= 0.9.10)
|
63
|
-
localhost (1.1.4)
|
64
61
|
mercenary (0.3.6)
|
65
|
-
|
66
|
-
mime-types-data (~> 3.2015)
|
67
|
-
mime-types-data (3.2019.1009)
|
62
|
+
method_source (1.0.0)
|
68
63
|
minitest (5.13.0)
|
69
64
|
minitest-reporters (1.4.2)
|
70
65
|
ansi
|
71
66
|
builder
|
72
67
|
minitest (>= 5.0)
|
73
68
|
ruby-progressbar
|
74
|
-
multi_xml (0.6.0)
|
75
69
|
mysql2 (0.5.3)
|
76
70
|
parallel (1.19.1)
|
77
71
|
parser (2.7.0.2)
|
@@ -79,7 +73,11 @@ GEM
|
|
79
73
|
pathutil (0.16.2)
|
80
74
|
forwardable-extended (~> 2.6)
|
81
75
|
pg (1.1.4)
|
76
|
+
pry (0.13.1)
|
77
|
+
coderay (~> 1.1)
|
78
|
+
method_source (~> 1.0)
|
82
79
|
public_suffix (4.0.3)
|
80
|
+
rack (2.2.3)
|
83
81
|
rainbow (3.0.0)
|
84
82
|
rake (12.3.3)
|
85
83
|
rake-compiler (1.0.5)
|
@@ -124,17 +122,17 @@ PLATFORMS
|
|
124
122
|
DEPENDENCIES
|
125
123
|
hiredis (= 0.6.3)
|
126
124
|
http_parser.rb (~> 0.6.0)
|
127
|
-
httparty (= 0.17.0)
|
128
125
|
jekyll (~> 3.8.6)
|
129
126
|
jekyll-remote-theme (~> 0.4.1)
|
130
127
|
jekyll-seo-tag (~> 2.6.1)
|
131
128
|
just-the-docs (~> 0.3.0)
|
132
|
-
localhost (= 1.1.4)
|
133
129
|
minitest (= 5.13.0)
|
134
130
|
minitest-reporters (= 1.4.2)
|
135
131
|
mysql2 (= 0.5.3)
|
136
132
|
pg (= 1.1.4)
|
137
133
|
polyphony!
|
134
|
+
pry (= 0.13.1)
|
135
|
+
rack (>= 2.0.8, < 2.3.0)
|
138
136
|
rake-compiler (= 1.0.5)
|
139
137
|
redis (= 4.1.0)
|
140
138
|
rubocop (= 0.85.1)
|
data/Rakefile
CHANGED
@@ -20,7 +20,7 @@ task :stress_test do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
task :docs do
|
23
|
-
exec 'RUBYOPT=-W0 jekyll serve -s docs -H ec2-
|
23
|
+
exec 'RUBYOPT=-W0 jekyll serve -s docs -H ec2-18-156-117-172.eu-central-1.compute.amazonaws.com'
|
24
24
|
end
|
25
25
|
|
26
26
|
CLEAN.include "**/*.o", "**/*.so", "**/*.bundle", "**/*.jar", "pkg", "tmp"
|
data/TODO.md
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
0.45
|
2
|
+
|
3
3
|
- Review all code
|
4
4
|
- Cleanup C code
|
5
|
-
- Cleanup Ruby code (use Rubocop)
|
6
5
|
- Cleanup and annotate examples (and remove all the examples used for
|
7
6
|
debugging). Focus on examples that serve as "how-to".
|
8
7
|
|
8
|
+
0.45.1
|
9
|
+
|
10
|
+
- Adapter for Pry and IRB (Which fixes #5 and #6)
|
11
|
+
|
12
|
+
0.46.0
|
13
|
+
|
9
14
|
- Debugging
|
10
15
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
11
16
|
test fails
|
@@ -118,7 +123,7 @@
|
|
118
123
|
- discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
|
119
124
|
|
120
125
|
|
121
|
-
## 0.
|
126
|
+
## 0.47
|
122
127
|
|
123
128
|
### Some more API work, more docs
|
124
129
|
|
@@ -131,13 +136,13 @@
|
|
131
136
|
- proceed from there
|
132
137
|
|
133
138
|
|
134
|
-
## 0.
|
139
|
+
## 0.48
|
135
140
|
|
136
141
|
### Sinatra / Sidekiq
|
137
142
|
|
138
143
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
139
144
|
|
140
|
-
## 0.
|
145
|
+
## 0.49
|
141
146
|
|
142
147
|
### Testing && Docs
|
143
148
|
|
@@ -149,7 +154,7 @@
|
|
149
154
|
- `IO.foreach`
|
150
155
|
- `Process.waitpid`
|
151
156
|
|
152
|
-
## 0.
|
157
|
+
## 0.50 DNS
|
153
158
|
|
154
159
|
### DNS client
|
155
160
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Polyphony 0.44.1
|
2
|
+
|
3
|
+
## More performance, more compatibility, more robustness
|
4
|
+
|
5
|
+
The last three weeks have been very busy for Polyphony. Since I first presented
|
6
|
+
Polyphony here and elsewhere, 17 issues were closed, 10 pull requests were
|
7
|
+
merged, and 144 commits were made by 4 different authors. I'm really
|
8
|
+
excited about Polyphony and the momentum it seems to be gathering. Your
|
9
|
+
reactions have been very positive so far (it even got [tweeted by
|
10
|
+
Matz!](https://twitter.com/yukihiro_matz/status/1279289318083715073))
|
11
|
+
|
12
|
+
I'm even more excited about the contributions Polyphony is starting to get from
|
13
|
+
other developers. Thank you [Will](https://github.com/wjordan),
|
14
|
+
[Máximo](https://github.com/ElMassimo) and [Trent](https://github.com/misfo) for
|
15
|
+
your valuable contributions! Also, the Polyphony project has now got a logo
|
16
|
+
designed by my friend [Gérald Morales](https://webocube.com/).
|
17
|
+
|
18
|
+
I'd like to encourage other developers to get in on the action and start
|
19
|
+
contributing by testing Polyphony, creating issues and writing code and
|
20
|
+
documentation. Together we can make Polyphony a game-changer for developing
|
21
|
+
concurrent apps in Ruby, and finally put to rest the notion that "Ruby is slow"!
|
22
|
+
|
23
|
+
Since the last public release of Polyphony, we have focused on fixing bugs,
|
24
|
+
improving performance and introducing new features that improve the Polyphony
|
25
|
+
developer experience. Polyphony 0.44 is up to 20% percent faster than the
|
26
|
+
previous release, due notably to a new ring-buffer implementation used by the
|
27
|
+
fiber run queue and the `Polyphony::Queue` class, a new `Backend#read_loop` API
|
28
|
+
for tighter server loops, and minimizing `fcntl` syscalls when doing I/O. These
|
29
|
+
and other minor improvements have resulted in Polyphony first crossing the
|
30
|
+
50,000 requests per second threshold for the first time in a minimal [rack
|
31
|
+
server
|
32
|
+
example](https://github.com/digital-fabric/polyphony/blob/master/examples/io/xx-rack_server.rb).
|
33
|
+
|
34
|
+
Notable new features include a MySQL adapter, a Sequel adapter, and a new
|
35
|
+
`Fiber#interject` API that allows executing arbitrary code on arbitrary fibers.
|
36
|
+
|
37
|
+
We have also fixed numerous bugs, among which an issue building Polyphony on
|
38
|
+
MacOS, problems issuing `Net::HTTP` requests with secure URLs, an issue with
|
39
|
+
`YAML.load` and much more...
|
40
|
+
|
41
|
+
For the full list of changes please consult the [change log](https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md).
|
42
|
+
|
43
|
+
## What's next for Polyphony?
|
44
|
+
|
45
|
+
The next release of Polyphony will focus on full support IRB and Pry. Being able
|
46
|
+
to run operations in the background in IRB and Pry can be very beneficial, most
|
47
|
+
of all when developing and when debugging running processes using `binding.pry`
|
48
|
+
for example.
|
49
|
+
|
50
|
+
Subsequent releases will introduce a whole new full-featured debugger for
|
51
|
+
fiber-aware concurrent apps, and eventually full support for Sequel, Sinatra,
|
52
|
+
Hanami, Sidekiq and other major areas of the Ruby ecosystem.
|
53
|
+
|
54
|
+
## Tipi - a polyphonic web server for Ruby
|
55
|
+
|
56
|
+
[Tipi](https://github.com/digital-fabric/tipi) is a new web server for Ruby
|
57
|
+
apps. It is intended to be *the* go-to app server for Ruby apps looking for
|
58
|
+
robustness, scalability and performance. Tipi already supports HTTP/1, HTTP/2,
|
59
|
+
WebSockets and SSL termination. It can currently drive simple Rack apps. In the
|
60
|
+
future Tipi will be fully compliant with the Rack specification, and will also
|
61
|
+
offer a static file server, a rich configuration and automatic TLS certificates
|
62
|
+
(using Let's Encrypt) out of the box.
|
63
|
+
|
64
|
+
For those wondering about performance, here are some preliminary numbers (see
|
65
|
+
disclaimer below):
|
66
|
+
|
67
|
+
- HTTP, hello world, single process: ~50000 requests/second
|
68
|
+
- HTTP, Rack hello world app, single process: ~33000 requests/second
|
69
|
+
- HTTP, Rack hello world, 4 worker processes: ~95000 requests/second
|
70
|
+
- HTTPS, hello world, single process: ~20000 requests/second
|
71
|
+
- HTTPS, hello world, 4 worker processes: ~72000 requests/second
|
72
|
+
|
73
|
+
Disclaimer: these numbers should be taken with a grain of salt. They do not
|
74
|
+
follow any established benchmarking methodology, and may vary significantly. The
|
75
|
+
different configurtations were benchmarked using the command: `wrk -d10 -t1 -c10
|
76
|
+
"<http|https>://127.0.0.1:1234/"` on the same machine (an `m2.xlarge` instance)
|
77
|
+
as the server. In the future Tipi's performance might substantially change. YMMV.
|
@@ -12,7 +12,7 @@ Polyphony enhances the core `Thread` class with APIs for switching and
|
|
12
12
|
scheduling fibers, and reimplements some of its APIs such as `Thread#raise`
|
13
13
|
using fibers which, incidentally, make it safe.
|
14
14
|
|
15
|
-
Each thread has its own run queue and its own system
|
15
|
+
Each thread has its own run queue and its own system backend. While running
|
16
16
|
multiple threads does not result in true parallelism in MRI Ruby, sometimes
|
17
17
|
multithreading is inevitable, for instance when using third-party gems that
|
18
18
|
spawn threads, or when calling blocking APIs that are not fiber-aware.
|
@@ -341,25 +341,25 @@ move_on_after(10) { perform_query }
|
|
341
341
|
cancel_after(10) { perform_query }
|
342
342
|
```
|
343
343
|
|
344
|
-
## The
|
344
|
+
## The Polyphony Backend
|
345
345
|
|
346
346
|
In order to implement automatic fiber switching when performing blocking
|
347
|
-
operations, Polyphony introduces a concept called the *system
|
348
|
-
|
347
|
+
operations, Polyphony introduces a concept called the *system backend*. The system
|
348
|
+
backend is an object having a uniform interface, that performs all blocking
|
349
349
|
operations.
|
350
350
|
|
351
351
|
While a standard event loop-based solution would implement a blocking call
|
352
|
-
separately from the fiber scheduling, the system
|
352
|
+
separately from the fiber scheduling, the system backend integrates the two to
|
353
353
|
create a blocking call that is already knows how to switch and schedule fibers.
|
354
354
|
For example, in Polyphony all APIs having to do with reading from files or
|
355
|
-
sockets end up calling `Thread.current.
|
355
|
+
sockets end up calling `Thread.current.backend.read`, which does all the work.
|
356
356
|
|
357
357
|
This design offers some major advantages over other designs. It minimizes memory
|
358
358
|
allocations, of both Ruby objects and C structures. For example, instead of
|
359
359
|
having to allocate libev watchers on the heap and then pass them around, they
|
360
360
|
are allocated on the stack instead, which saves up on both memory and CPU cycles.
|
361
361
|
|
362
|
-
In addition, the
|
362
|
+
In addition, the backend interface includes two methods that allow maximizing
|
363
363
|
server performance by accepting connections and reading from sockets in a tight
|
364
364
|
loop. Here's a naive implementation of an HTTP/1 server:
|
365
365
|
|
@@ -372,7 +372,7 @@ def handle_client(socket)
|
|
372
372
|
reqs = []
|
373
373
|
parser.on_message_complete = proc { |env| reqs << { foo: :bar } }
|
374
374
|
|
375
|
-
Thread.current.
|
375
|
+
Thread.current.backend.read_loop(socket) do |data|
|
376
376
|
parser << data
|
377
377
|
reqs.each { |r| reply(socket, r) }
|
378
378
|
reqs.clear
|
@@ -388,20 +388,20 @@ end
|
|
388
388
|
server = TCPServer.open('0.0.0.0', 1234)
|
389
389
|
puts "listening on port 1234"
|
390
390
|
|
391
|
-
Thread.current.
|
391
|
+
Thread.current.backend.accept_loop(server) do |client|
|
392
392
|
spin { handle_client(client) }
|
393
393
|
end
|
394
394
|
```
|
395
395
|
|
396
|
-
The `#read_loop` and `#accept_loop`
|
396
|
+
The `#read_loop` and `#accept_loop` backend methods implement tight loops that
|
397
397
|
provide a significant boost to performance (up to +30% better throughput.)
|
398
398
|
|
399
|
-
Currently, Polyphony includes a single system
|
399
|
+
Currently, Polyphony includes a single system backend based on
|
400
400
|
[libev](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod). In the future,
|
401
|
-
Polyphony will include other platform-specific system
|
402
|
-
|
401
|
+
Polyphony will include other platform-specific system backends, such as a Windows
|
402
|
+
backend using
|
403
403
|
[IOCP](https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports),
|
404
|
-
or an [io_uring](https://unixism.net/loti/what_is_io_uring.html)
|
404
|
+
or an [io_uring](https://unixism.net/loti/what_is_io_uring.html) backend,
|
405
405
|
which might be a game-changer for writing highly-concurrent Ruby-based web apps.
|
406
406
|
|
407
407
|
## Writing Web Apps with Polyphony
|
@@ -482,5 +482,5 @@ reach version 1.0. Here are some of the exciting directions we're working on.
|
|
482
482
|
|
483
483
|
- Support for more core and stdlib APIs
|
484
484
|
- More adapters for gems with C-extensions, such as `mysql`, `sqlite3` etc
|
485
|
-
- Use `io_uring`
|
485
|
+
- Use `io_uring` backend as alternative to the libev backend
|
486
486
|
- More concurrency constructs for building highly concurrent applications
|
@@ -123,7 +123,7 @@ suspend # The main fiber suspends, waiting for all other work to finish
|
|
123
123
|
sleep 1 # The sleeper fiber goes to sleep
|
124
124
|
Gyro::Timer.new(1, 0).await # A timer event watcher is setup and yields
|
125
125
|
Thread.current.switch_fiber # Polyphony looks for other runnable fibers
|
126
|
-
Thread.current.
|
126
|
+
Thread.current.backend.poll # With no work left, the event loop is ran
|
127
127
|
fiber.schedule # The timer event fires, scheduling the sleeper fiber
|
128
128
|
# <= The sleep method returns
|
129
129
|
puts "Woke up"
|
@@ -27,17 +27,17 @@ class Test
|
|
27
27
|
|
28
28
|
def test_file
|
29
29
|
f = File.open(__FILE__, 'r')
|
30
|
-
puts Thread.current.
|
30
|
+
puts Thread.current.backend.read(f, +'', 10000, true)
|
31
31
|
|
32
|
-
Thread.current.
|
32
|
+
Thread.current.backend.write(STDOUT, "Write something: ")
|
33
33
|
str = +''
|
34
|
-
Thread.current.
|
34
|
+
Thread.current.backend.read(STDIN, str, 5, false)
|
35
35
|
puts str
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_fork
|
39
39
|
pid = fork do
|
40
|
-
Thread.current.
|
40
|
+
Thread.current.backend.post_fork
|
41
41
|
puts 'child going to sleep'
|
42
42
|
sleep 1
|
43
43
|
puts 'child done sleeping'
|
@@ -45,7 +45,7 @@ class Test
|
|
45
45
|
end
|
46
46
|
|
47
47
|
puts "Waiting for pid #{pid}"
|
48
|
-
result = Thread.current.
|
48
|
+
result = Thread.current.backend.waitpid(pid)
|
49
49
|
puts "Done waiting"
|
50
50
|
p result
|
51
51
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'polyphony/adapters/readline'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
$counter = 0
|
9
|
+
timer = spin do
|
10
|
+
throttled_loop(5) do
|
11
|
+
$counter += 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
at_exit { timer.stop }
|
16
|
+
|
17
|
+
puts 'try typing $counter to see the counter incremented in the background'
|
18
|
+
binding.pry
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'http/parser'
|
6
|
+
require 'rack'
|
7
|
+
|
8
|
+
module RackAdapter
|
9
|
+
class << self
|
10
|
+
def run(app)
|
11
|
+
->(socket, req) { respond(socket, req, app.(env(req))) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def env(req)
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
|
18
|
+
def respond(socket, request, (status_code, headers, body))
|
19
|
+
body = body.join
|
20
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\n"
|
21
|
+
socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{body}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
$connection_count = 0
|
27
|
+
|
28
|
+
def handle_client(socket, &handler)
|
29
|
+
$connection_count += 1
|
30
|
+
parser = Http::Parser.new
|
31
|
+
reqs = []
|
32
|
+
parser.on_message_complete = proc do |env|
|
33
|
+
reqs << Object.new # parser
|
34
|
+
end
|
35
|
+
socket.read_loop do |data|
|
36
|
+
parser << data
|
37
|
+
while (req = reqs.shift)
|
38
|
+
handler.call(socket, req)
|
39
|
+
req = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
rescue IOError, SystemCallError => e
|
43
|
+
# do nothing
|
44
|
+
ensure
|
45
|
+
$connection_count -= 1
|
46
|
+
socket&.close
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_request(client, parser)
|
50
|
+
status_code = "200 OK"
|
51
|
+
data = "Hello world!\n"
|
52
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
|
53
|
+
client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
54
|
+
end
|
55
|
+
|
56
|
+
server = TCPServer.open('0.0.0.0', 1234)
|
57
|
+
puts "pid #{Process.pid}"
|
58
|
+
puts "listening on port 1234"
|
59
|
+
|
60
|
+
app = RackAdapter.run(lambda { |env|
|
61
|
+
[
|
62
|
+
200,
|
63
|
+
{"Content-Type" => "text/plain"},
|
64
|
+
["Hello, world!\n"]
|
65
|
+
]
|
66
|
+
})
|
67
|
+
|
68
|
+
loop do
|
69
|
+
client = server.accept
|
70
|
+
spin { handle_client(client, &app) }
|
71
|
+
end
|