polyphony 0.44.0 → 0.45.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|