uringmachine 0.31.0 → 0.32.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/CHANGELOG.md +4 -0
- data/README.md +36 -37
- data/TODO.md +18 -55
- data/benchmark/bm_io_pipe.rb +2 -2
- data/benchmark/common.rb +16 -16
- data/benchmark/gets.rb +7 -7
- data/benchmark/gets_concurrent.rb +12 -12
- data/benchmark/http_parse.rb +11 -11
- data/benchmark/http_server_accept_queue.rb +7 -7
- data/benchmark/http_server_multi_accept.rb +7 -7
- data/benchmark/http_server_multi_ractor.rb +7 -7
- data/benchmark/http_server_single_thread.rb +8 -8
- data/benchmark/openssl.rb +4 -4
- data/examples/fiber_concurrency_io.rb +1 -1
- data/examples/fiber_concurrency_um.rb +16 -0
- data/examples/io_uring_simple.c +13 -4
- data/examples/pg.rb +2 -2
- data/examples/um_cancellation.rb +20 -0
- data/examples/um_fiber_scheduler.rb +10 -0
- data/examples/um_io.rb +19 -0
- data/examples/um_mo.c +32 -0
- data/examples/um_multishot.rb +15 -0
- data/examples/um_ssl.rb +11 -0
- data/ext/um/um.h +19 -19
- data/ext/um/um_ext.c +2 -4
- data/ext/um/{um_connection.c → um_io.c} +210 -210
- data/ext/um/{um_connection_class.c → um_io_class.c} +102 -102
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +12 -12
- data/test/{test_connection.rb → test_io.rb} +29 -29
- data/test/test_um.rb +7 -7
- metadata +13 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 299bd9cc7810b3d67352cf1c35e1bad8228d26dc1495d2e03e8283299347836a
|
|
4
|
+
data.tar.gz: 1241fd5922a26d0223a6d345984e7548bb4486160a5f7f70cb25f4356cde4736
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0075bd142ba474e475eb53fca3eb8e88bbb5fdba8214d7832b34582f3089e2720e99e069f49d7244b2c2cd9f5536ca5113589b9a5658d3f3679e3d6352043e1d
|
|
7
|
+
data.tar.gz: fe68ab2c66601a0aa5786e86b86173b5c6b94a1f58d0a7151546c4c596b1d625912ebd32670c8230ab301886f3a6a0a35edff1cd7a472f5253cb181e0156a1f7
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ implementation that allows integration with the entire Ruby ecosystem.
|
|
|
37
37
|
- Excellent performance characteristics for concurrent I/O-bound applications.
|
|
38
38
|
- `Fiber::Scheduler` implementation to automatically integrate with the Ruby
|
|
39
39
|
ecosystem in a transparent fashion.
|
|
40
|
-
- [
|
|
40
|
+
- [IO](#io-api) class with automatic buffer management for reading.
|
|
41
41
|
- Optimized I/O for encrypted SSL connections.
|
|
42
42
|
|
|
43
43
|
## Design
|
|
@@ -286,70 +286,69 @@ fiber = Fiber.schedule do
|
|
|
286
286
|
end
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
##
|
|
289
|
+
## IO API
|
|
290
290
|
|
|
291
|
-
`UringMachine::
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
both line-based protocols and binary (frame-based) protocols.
|
|
291
|
+
`UringMachine::IO` is a class designed for efficiently read from and write to a
|
|
292
|
+
socket or other file descriptor. The IO class is ideal for implementing
|
|
293
|
+
line-based and binary (frame-based) protocols.
|
|
295
294
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
An IO is associated with a UringMachine instance and a target file descriptor
|
|
296
|
+
(or SSL socket, see also [IO modes](#io-modes) below). Behind the scenes, the IO
|
|
297
|
+
class takes advantage of io_uring's provided buffers feature, and more recently,
|
|
298
|
+
the introduction of [incremental buffer
|
|
300
299
|
consumption](https://github.com/axboe/liburing/wiki/What's-new-with-io_uring-in-6.11-and-6.12#incremental-provided-buffer-consumption).
|
|
301
300
|
|
|
302
|
-
When
|
|
301
|
+
When IO instances are used, UringMachine automatically manages the buffers it
|
|
303
302
|
provides to the kernel, maximizing buffer reuse and minimizing allocations.
|
|
304
303
|
UringMachine also responds to stress conditions (increased incoming traffic) by
|
|
305
304
|
automatically provisioning additional buffers.
|
|
306
305
|
|
|
307
|
-
To create
|
|
306
|
+
To create an IO for a given fd, use `UM#io`:
|
|
308
307
|
|
|
309
308
|
```ruby
|
|
310
|
-
|
|
309
|
+
io = machine.io(fd)
|
|
311
310
|
|
|
312
|
-
# you can
|
|
313
|
-
machine.
|
|
311
|
+
# you can provide a block that will be passed the IO instance:
|
|
312
|
+
machine.io(fd) { |io| do_something_with(io) }
|
|
314
313
|
|
|
315
|
-
# you can also instantiate
|
|
316
|
-
|
|
314
|
+
# you can also instantiate an IO directly:
|
|
315
|
+
io = UM::IO.new(machine, fd)
|
|
317
316
|
```
|
|
318
317
|
|
|
319
|
-
The following API is used to interact with
|
|
318
|
+
The following API is used to interact with an IO:
|
|
320
319
|
|
|
321
320
|
```ruby
|
|
322
321
|
# Read until a newline character is encountered:
|
|
323
|
-
line =
|
|
322
|
+
line = io.read_line(0)
|
|
324
323
|
|
|
325
324
|
# Read line with a maximum length of 13 bytes:
|
|
326
|
-
line =
|
|
325
|
+
line = io.read_line(13)
|
|
327
326
|
|
|
328
327
|
# Read all data:
|
|
329
|
-
buf =
|
|
328
|
+
buf = io.read(0)
|
|
330
329
|
|
|
331
330
|
# Read exactly 13 bytes:
|
|
332
|
-
buf =
|
|
331
|
+
buf = io.read(13)
|
|
333
332
|
|
|
334
333
|
# Read up to 13 bytes:
|
|
335
|
-
buf =
|
|
334
|
+
buf = io.read(-13)
|
|
336
335
|
|
|
337
336
|
# Read continuously until EOF
|
|
338
|
-
|
|
337
|
+
io.read_each { |data| ... }
|
|
339
338
|
|
|
340
339
|
# Skip 3 bytes:
|
|
341
|
-
|
|
340
|
+
io.skip(3)
|
|
342
341
|
|
|
343
342
|
# Write
|
|
344
|
-
|
|
343
|
+
io.write('foo', 'bar', 'baz')
|
|
345
344
|
```
|
|
346
345
|
|
|
347
346
|
Here's an example of a how a basic HTTP request parser might be implemented
|
|
348
|
-
using a
|
|
347
|
+
using a `UM::IO`:
|
|
349
348
|
|
|
350
349
|
```ruby
|
|
351
|
-
def parse_http_request_headers(
|
|
352
|
-
request_line =
|
|
350
|
+
def parse_http_request_headers(io)
|
|
351
|
+
request_line = io.read_line(0)
|
|
353
352
|
m = request_line.match(REQUEST_LINE_RE)
|
|
354
353
|
return nil if !m
|
|
355
354
|
|
|
@@ -360,7 +359,7 @@ def parse_http_request_headers(conn)
|
|
|
360
359
|
}
|
|
361
360
|
|
|
362
361
|
while true
|
|
363
|
-
line =
|
|
362
|
+
line = io.read_line(0)
|
|
364
363
|
break if !line || line.empty?
|
|
365
364
|
|
|
366
365
|
m = line.match(HEADER_RE)
|
|
@@ -370,26 +369,26 @@ def parse_http_request_headers(conn)
|
|
|
370
369
|
end
|
|
371
370
|
```
|
|
372
371
|
|
|
373
|
-
###
|
|
372
|
+
### IO modes
|
|
374
373
|
|
|
375
|
-
|
|
376
|
-
|
|
374
|
+
IO modes allow IOs to be transport agnostic. The following modes are currently
|
|
375
|
+
supported:
|
|
377
376
|
|
|
378
377
|
- `:fd` - use the buffer pool, read data using multishot read
|
|
379
378
|
(this is the default mode).
|
|
380
379
|
- `:socket` - use the buffer pool, read data using multishot recv.
|
|
381
380
|
- `:ssl` - read from an `SSLSocket` object.
|
|
382
381
|
|
|
383
|
-
The mode is specified as an additional argument to `
|
|
382
|
+
The mode is specified as an additional argument to `IO.new`:
|
|
384
383
|
|
|
385
384
|
```ruby
|
|
386
385
|
# using recv/send:
|
|
387
|
-
|
|
386
|
+
io = machine.io(fd, :socket)
|
|
388
387
|
|
|
389
388
|
# SSL I/O:
|
|
390
|
-
|
|
389
|
+
io = machine.io(ssl, :ssl)
|
|
391
390
|
# or simply:
|
|
392
|
-
|
|
391
|
+
io = machine.io(ssl)
|
|
393
392
|
```
|
|
394
393
|
|
|
395
394
|
## Performance
|
data/TODO.md
CHANGED
|
@@ -1,63 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
- Add tests for support for Set in `machine#await`
|
|
4
|
-
- Add tests for support for Set, Array in `machine#join`
|
|
5
|
-
- Add `UM#read_file` for reading entire file
|
|
6
|
-
- Add `UM#write_file` for writing entire file
|
|
7
|
-
- Rename stream methods: `:fd`, `:socket`, `:ssl`
|
|
8
|
-
|
|
9
|
-
## Improving streams
|
|
10
|
-
|
|
11
|
-
One wart of the stream API is that it's only for reading, so if we want to
|
|
12
|
-
implement a protocol where we read and write to a target fd, we also need to
|
|
13
|
-
keep the fd around or call `stream.target` every time we want to write to it,
|
|
14
|
-
*and* we don't have a transport-agnostic write op.
|
|
1
|
+
- Rename Connection to IO
|
|
15
2
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
while l = conn.read_line
|
|
22
|
-
conn.write(l, '\n')
|
|
23
|
-
end
|
|
24
|
-
# or:
|
|
25
|
-
buf = conn.read(42)
|
|
26
|
-
```
|
|
3
|
+
```ruby
|
|
4
|
+
io = machine.io(fd)
|
|
5
|
+
l = io.read_line(4)
|
|
6
|
+
io.write('foo')
|
|
7
|
+
```
|
|
27
8
|
|
|
28
|
-
|
|
9
|
+
- Add `IO#fd`/`IO#target` method
|
|
29
10
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
reply = conn.resp_read
|
|
33
|
-
```
|
|
11
|
+
- Add `UM#inspect` (show size, modes)
|
|
12
|
+
- Add `UM::IO#inspect` (show target, mode, pending bytes)
|
|
34
13
|
|
|
35
|
-
|
|
14
|
+
## Reimplement multishot read/recv using buffer pool
|
|
36
15
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
conn.http_write_response({ ':status' => 200 }, 'foo')
|
|
16
|
+
- remove `#setup_buffer_ring` method
|
|
17
|
+
- use buffer pool, just like UM::Connection
|
|
40
18
|
|
|
41
|
-
|
|
42
|
-
conn.http_write_request({ ':method' => 'GET', ':path' => '/foo' }, nil)
|
|
43
|
-
```
|
|
19
|
+
## immediate
|
|
44
20
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- [v] rename `#read_to_delim` to `#read_to_delim`
|
|
51
|
-
- [v] rename `#each` to `#read_each`
|
|
52
|
-
- [v] rename `#resp_decode` to `#resp_read`
|
|
53
|
-
- Rename modes:
|
|
54
|
-
- [v] :fd to :fd
|
|
55
|
-
- [v] :socket to :socket
|
|
56
|
-
- [v] auto detect SSL
|
|
57
|
-
- Rename `Stream` to `Connection`
|
|
58
|
-
- Add methods:
|
|
59
|
-
- `#write(*bufs)`
|
|
60
|
-
- `#resp_write(obj)`
|
|
21
|
+
- Add tests for support for Set in `machine#await`
|
|
22
|
+
- Add tests for support for Set, Array in `machine#join`
|
|
23
|
+
- Add `UM#read_file` for reading entire file
|
|
24
|
+
- Add `UM#write_file` for writing entire file
|
|
25
|
+
- Rename stream methods: `:fd`, `:socket`, `:ssl`
|
|
61
26
|
|
|
62
27
|
## Balancing I/O with the runqueue
|
|
63
28
|
|
|
@@ -124,11 +89,9 @@ Plan of action:
|
|
|
124
89
|
|
|
125
90
|
## ops still not implemented
|
|
126
91
|
|
|
127
|
-
- splice / - tee
|
|
128
92
|
- sendto
|
|
129
93
|
- recvfrom
|
|
130
94
|
- poll_multishot
|
|
131
|
-
- fsync
|
|
132
95
|
- mkdir / mkdirat
|
|
133
96
|
- link / linkat / unlink / unlinkat / symlink
|
|
134
97
|
- rename / renameat
|
data/benchmark/bm_io_pipe.rb
CHANGED
data/benchmark/common.rb
CHANGED
|
@@ -62,35 +62,35 @@ class UMBenchmark
|
|
|
62
62
|
# baseline_um: [:baseline_um, "UM no concurrency"],
|
|
63
63
|
# thread_pool: [:thread_pool, "ThreadPool"],
|
|
64
64
|
|
|
65
|
-
threads: [:threads, "Threads"],
|
|
65
|
+
# threads: [:threads, "Threads"],
|
|
66
66
|
|
|
67
|
-
async_uring: [:scheduler, "Async uring"],
|
|
68
|
-
async_uring_x2: [:scheduler_x, "Async uring x2"],
|
|
67
|
+
# async_uring: [:scheduler, "Async uring"],
|
|
68
|
+
# async_uring_x2: [:scheduler_x, "Async uring x2"],
|
|
69
69
|
|
|
70
70
|
# async_epoll: [:scheduler, "Async epoll"],
|
|
71
71
|
# async_epoll_x2: [:scheduler_x, "Async epoll x2"],
|
|
72
72
|
|
|
73
|
-
um_fs: [:scheduler, "UM FS"],
|
|
74
|
-
um_fs_x2: [:scheduler_x, "UM FS x2"],
|
|
73
|
+
# um_fs: [:scheduler, "UM FS"],
|
|
74
|
+
# um_fs_x2: [:scheduler_x, "UM FS x2"],
|
|
75
75
|
|
|
76
|
-
um: [:um, "UM"],
|
|
77
|
-
um_sidecar: [:um, "UM sidecar"],
|
|
76
|
+
# um: [:um, "UM"],
|
|
77
|
+
# um_sidecar: [:um, "UM sidecar"],
|
|
78
78
|
# um_sqpoll: [:um, "UM sqpoll"],
|
|
79
79
|
um_x2: [:um_x, "UM x2"],
|
|
80
|
-
um_x4: [:um_x, "UM x4"],
|
|
81
|
-
um_x8: [:um_x, "UM x8"],
|
|
80
|
+
# um_x4: [:um_x, "UM x4"],
|
|
81
|
+
# um_x8: [:um_x, "UM x8"],
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
def run_benchmarks(b)
|
|
85
85
|
STDOUT.sync = true
|
|
86
86
|
@@benchmarks.each do |sym, (doer, name)|
|
|
87
87
|
if respond_to?(:"do_#{doer}")
|
|
88
|
-
STDOUT << "Running #{name}... "
|
|
89
|
-
ts = nil
|
|
88
|
+
# STDOUT << "Running #{name}... "
|
|
89
|
+
# ts = nil
|
|
90
90
|
b.report(name) {
|
|
91
|
-
ts = measure_time { send(:"run_#{sym}") }
|
|
91
|
+
# ts = measure_time { send(:"run_#{sym}") }
|
|
92
|
+
send(:"run_#{sym}")
|
|
92
93
|
}
|
|
93
|
-
p ts
|
|
94
94
|
cleanup
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -197,7 +197,7 @@ class UMBenchmark
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
def run_um
|
|
200
|
-
machine = UM.new
|
|
200
|
+
machine = UM.new(size: 16384)
|
|
201
201
|
fibers = []
|
|
202
202
|
fds = []
|
|
203
203
|
do_um(machine, fibers, fds)
|
|
@@ -226,7 +226,7 @@ class UMBenchmark
|
|
|
226
226
|
def run_um_x2
|
|
227
227
|
threads = 2.times.map do
|
|
228
228
|
Thread.new do
|
|
229
|
-
machine = UM.new
|
|
229
|
+
machine = UM.new(size: 16384)
|
|
230
230
|
fibers = []
|
|
231
231
|
fds = []
|
|
232
232
|
do_um_x(2, machine, fibers, fds)
|
|
@@ -240,7 +240,7 @@ class UMBenchmark
|
|
|
240
240
|
def run_um_x4
|
|
241
241
|
threads = 4.times.map do
|
|
242
242
|
Thread.new do
|
|
243
|
-
machine = UM.new
|
|
243
|
+
machine = UM.new(size: 16384)
|
|
244
244
|
fibers = []
|
|
245
245
|
fds = []
|
|
246
246
|
do_um_x(4, machine, fibers, fds)
|
data/benchmark/gets.rb
CHANGED
|
@@ -34,16 +34,16 @@ def um_read
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
def
|
|
40
|
-
@
|
|
37
|
+
@fd_io = @machine.open('/dev/random', UM::O_RDONLY)
|
|
38
|
+
@io = UM::IO.new(@machine, @fd_io)
|
|
39
|
+
def um_io_read_line
|
|
40
|
+
@io².read_line(0)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
Benchmark.ips do |x|
|
|
44
|
-
x.report('IO#gets')
|
|
45
|
-
x.report('UM#read+buf')
|
|
46
|
-
x.report('UM::
|
|
44
|
+
x.report('IO#gets') { io_gets }
|
|
45
|
+
x.report('UM#read+buf') { um_read }
|
|
46
|
+
x.report('UM::IO') { um_io_read_line }
|
|
47
47
|
|
|
48
48
|
x.compare!(order: :baseline)
|
|
49
49
|
end
|
|
@@ -83,40 +83,40 @@ ensure
|
|
|
83
83
|
stop_server
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
@
|
|
87
|
-
def
|
|
86
|
+
@total_io = 0
|
|
87
|
+
def um_io_do
|
|
88
88
|
# fd = @machine.open('/dev/random', UM::O_RDONLY)
|
|
89
89
|
fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
90
90
|
@machine.connect(fd, '127.0.0.1', 1234)
|
|
91
|
-
|
|
92
|
-
N.times { @
|
|
91
|
+
io = UM::IO.new(@machine, fd)
|
|
92
|
+
N.times { @total_io += io.read_line(0)&.bytesize || 0 }
|
|
93
93
|
rescue => e
|
|
94
94
|
p e
|
|
95
95
|
p e.backtrace
|
|
96
96
|
ensure
|
|
97
|
-
|
|
97
|
+
io.clear
|
|
98
98
|
@machine.close(fd)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def um_io
|
|
102
102
|
start_server
|
|
103
103
|
ff = C.times.map {
|
|
104
104
|
@machine.snooze
|
|
105
|
-
@machine.spin {
|
|
105
|
+
@machine.spin { um_io_do }
|
|
106
106
|
}
|
|
107
107
|
@machine.await(ff)
|
|
108
|
-
pp total: @
|
|
108
|
+
pp total: @total_io
|
|
109
109
|
ensure
|
|
110
110
|
stop_server
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
p(C:, N:)
|
|
114
|
-
|
|
114
|
+
um_io
|
|
115
115
|
pp @machine.metrics
|
|
116
116
|
exit
|
|
117
117
|
|
|
118
118
|
Benchmark.bm do
|
|
119
|
-
it.report('Thread/IO#gets')
|
|
120
|
-
it.report('Fiber/UM#read+buf')
|
|
121
|
-
it.report('Fiber/UM::
|
|
119
|
+
it.report('Thread/IO#gets') { io_gets }
|
|
120
|
+
it.report('Fiber/UM#read+buf') { um_read }
|
|
121
|
+
it.report('Fiber/UM::IO') { um_io }
|
|
122
122
|
end
|
data/benchmark/http_parse.rb
CHANGED
|
@@ -129,15 +129,15 @@ ensure
|
|
|
129
129
|
($machine.close(wfd) rescue nil) if wfd
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
def
|
|
133
|
-
|
|
132
|
+
def io_parse_headers(fd)
|
|
133
|
+
io = UM::IO.new($machine, fd)
|
|
134
134
|
|
|
135
135
|
buf = String.new(capacity: 65536)
|
|
136
|
-
headers =
|
|
136
|
+
headers = io_get_request_line(io, buf)
|
|
137
137
|
return nil if !headers
|
|
138
138
|
|
|
139
139
|
while true
|
|
140
|
-
line =
|
|
140
|
+
line = io.read_line(0)
|
|
141
141
|
break if line.empty?
|
|
142
142
|
|
|
143
143
|
m = line.match(RE_HEADER_LINE)
|
|
@@ -149,8 +149,8 @@ def connection_parse_headers(fd)
|
|
|
149
149
|
headers
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
-
def
|
|
153
|
-
line =
|
|
152
|
+
def io_get_request_line(io, buf)
|
|
153
|
+
line = io.read_line(0)
|
|
154
154
|
|
|
155
155
|
m = line.match(RE_REQUEST_LINE)
|
|
156
156
|
return nil if !m
|
|
@@ -162,12 +162,12 @@ def connection_get_request_line(conn, buf)
|
|
|
162
162
|
}
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
def
|
|
165
|
+
def parse_http_io
|
|
166
166
|
rfd, wfd = UM.pipe
|
|
167
167
|
queue = UM::Queue.new
|
|
168
168
|
|
|
169
169
|
$machine.spin do
|
|
170
|
-
headers =
|
|
170
|
+
headers = io_parse_headers(rfd)
|
|
171
171
|
$machine.push(queue, headers)
|
|
172
172
|
rescue Exception => e
|
|
173
173
|
p e
|
|
@@ -188,7 +188,7 @@ def compare_allocs
|
|
|
188
188
|
p(
|
|
189
189
|
alloc_http_parser: alloc_count { x.times { parse_http_parser } },
|
|
190
190
|
alloc_stringio: alloc_count { x.times { parse_http_stringio } },
|
|
191
|
-
|
|
191
|
+
alloc_io: alloc_count { x.times { parse_http_io } }
|
|
192
192
|
)
|
|
193
193
|
ensure
|
|
194
194
|
GC.enable
|
|
@@ -213,8 +213,8 @@ def benchmark
|
|
|
213
213
|
x.config(:time => 5, :warmup => 3)
|
|
214
214
|
|
|
215
215
|
x.report("http_parser") { parse_http_parser }
|
|
216
|
-
x.report("
|
|
217
|
-
x.report("
|
|
216
|
+
x.report("StringIO") { parse_http_stringio }
|
|
217
|
+
x.report("UM::IO") { parse_http_io }
|
|
218
218
|
|
|
219
219
|
x.compare!
|
|
220
220
|
end
|
|
@@ -12,8 +12,8 @@ require 'uringmachine'
|
|
|
12
12
|
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
|
|
13
13
|
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
line =
|
|
15
|
+
def io_get_request_line(io, buf)
|
|
16
|
+
line = io.read_line(0)
|
|
17
17
|
m = line&.match(RE_REQUEST_LINE)
|
|
18
18
|
return nil if !m
|
|
19
19
|
|
|
@@ -26,12 +26,12 @@ end
|
|
|
26
26
|
|
|
27
27
|
class InvalidHeadersError < StandardError; end
|
|
28
28
|
|
|
29
|
-
def get_headers(
|
|
30
|
-
headers =
|
|
29
|
+
def get_headers(io, buf)
|
|
30
|
+
headers = io_get_request_line(io, buf)
|
|
31
31
|
return nil if !headers
|
|
32
32
|
|
|
33
33
|
while true
|
|
34
|
-
line =
|
|
34
|
+
line = io.read_line(0)
|
|
35
35
|
break if line.empty?
|
|
36
36
|
|
|
37
37
|
m = line.match(RE_HEADER_LINE)
|
|
@@ -51,11 +51,11 @@ def send_response(machine, fd)
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def handle_connection(machine, fd)
|
|
54
|
-
|
|
54
|
+
io = UM::IO.new(machine, fd)
|
|
55
55
|
buf = String.new(capacity: 65536)
|
|
56
56
|
|
|
57
57
|
while true
|
|
58
|
-
headers = get_headers(
|
|
58
|
+
headers = get_headers(io, buf)
|
|
59
59
|
break if !headers
|
|
60
60
|
|
|
61
61
|
send_response(machine, fd)
|
|
@@ -12,8 +12,8 @@ require 'uringmachine'
|
|
|
12
12
|
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
|
|
13
13
|
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
line =
|
|
15
|
+
def io_get_request_line(io, buf)
|
|
16
|
+
line = io.read_line(0)
|
|
17
17
|
m = line&.match(RE_REQUEST_LINE)
|
|
18
18
|
return nil if !m
|
|
19
19
|
|
|
@@ -26,12 +26,12 @@ end
|
|
|
26
26
|
|
|
27
27
|
class InvalidHeadersError < StandardError; end
|
|
28
28
|
|
|
29
|
-
def get_headers(
|
|
30
|
-
headers =
|
|
29
|
+
def get_headers(io, buf)
|
|
30
|
+
headers = io_get_request_line(io, buf)
|
|
31
31
|
return nil if !headers
|
|
32
32
|
|
|
33
33
|
while true
|
|
34
|
-
line =
|
|
34
|
+
line = io.read_line(0)
|
|
35
35
|
break if line.empty?
|
|
36
36
|
|
|
37
37
|
m = line.match(RE_HEADER_LINE)
|
|
@@ -51,11 +51,11 @@ def send_response(machine, fd)
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def handle_connection(machine, fd)
|
|
54
|
-
|
|
54
|
+
io = UM::IO.new(machine, fd)
|
|
55
55
|
buf = String.new(capacity: 65536)
|
|
56
56
|
|
|
57
57
|
while true
|
|
58
|
-
headers = get_headers(
|
|
58
|
+
headers = get_headers(io, buf)
|
|
59
59
|
break if !headers
|
|
60
60
|
|
|
61
61
|
send_response(machine, fd)
|
|
@@ -12,8 +12,8 @@ require 'uringmachine'
|
|
|
12
12
|
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
|
|
13
13
|
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
line =
|
|
15
|
+
def io_get_request_line(io, buf)
|
|
16
|
+
line = io.read_line(0)
|
|
17
17
|
m = line&.match(RE_REQUEST_LINE)
|
|
18
18
|
return nil if !m
|
|
19
19
|
|
|
@@ -26,12 +26,12 @@ end
|
|
|
26
26
|
|
|
27
27
|
class InvalidHeadersError < StandardError; end
|
|
28
28
|
|
|
29
|
-
def get_headers(
|
|
30
|
-
headers =
|
|
29
|
+
def get_headers(io, buf)
|
|
30
|
+
headers = io_get_request_line(io, buf)
|
|
31
31
|
return nil if !headers
|
|
32
32
|
|
|
33
33
|
while true
|
|
34
|
-
line =
|
|
34
|
+
line = io.read_line(0)
|
|
35
35
|
break if line.empty?
|
|
36
36
|
|
|
37
37
|
m = line.match(RE_HEADER_LINE)
|
|
@@ -53,11 +53,11 @@ end
|
|
|
53
53
|
|
|
54
54
|
def handle_connection(machine, fd)
|
|
55
55
|
machine.setsockopt(fd, UM::IPPROTO_TCP, UM::TCP_NODELAY, true)
|
|
56
|
-
|
|
56
|
+
io = UM::IO.new(machine, fd)
|
|
57
57
|
buf = String.new(capacity: 65536)
|
|
58
58
|
|
|
59
59
|
while true
|
|
60
|
-
headers = get_headers(
|
|
60
|
+
headers = get_headers(io, buf)
|
|
61
61
|
break if !headers
|
|
62
62
|
|
|
63
63
|
send_response(machine, fd)
|
|
@@ -12,8 +12,8 @@ require 'uringmachine'
|
|
|
12
12
|
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
|
|
13
13
|
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
line =
|
|
15
|
+
def io_get_request_line(io, buf)
|
|
16
|
+
line = io.read_line(0)
|
|
17
17
|
m = line&.match(RE_REQUEST_LINE)
|
|
18
18
|
return nil if !m
|
|
19
19
|
|
|
@@ -26,12 +26,12 @@ end
|
|
|
26
26
|
|
|
27
27
|
class InvalidHeadersError < StandardError; end
|
|
28
28
|
|
|
29
|
-
def get_headers(
|
|
30
|
-
headers =
|
|
29
|
+
def get_headers(io, buf)
|
|
30
|
+
headers = io_get_request_line(io, buf)
|
|
31
31
|
return nil if !headers
|
|
32
32
|
|
|
33
33
|
while true
|
|
34
|
-
line =
|
|
34
|
+
line = io.read_line(0)
|
|
35
35
|
break if line.empty?
|
|
36
36
|
|
|
37
37
|
m = line.match(RE_HEADER_LINE)
|
|
@@ -52,11 +52,11 @@ end
|
|
|
52
52
|
|
|
53
53
|
def handle_connection(machine, fd)
|
|
54
54
|
machine.setsockopt(fd, UM::IPPROTO_TCP, UM::TCP_NODELAY, true)
|
|
55
|
-
|
|
55
|
+
io = UM::IO.new(machine, fd)
|
|
56
56
|
buf = String.new(capacity: 65536)
|
|
57
57
|
|
|
58
58
|
while true
|
|
59
|
-
headers = get_headers(
|
|
59
|
+
headers = get_headers(io, buf)
|
|
60
60
|
break if !headers
|
|
61
61
|
|
|
62
62
|
send_response(machine, fd)
|
|
@@ -77,4 +77,4 @@ machine.bind(fd, '127.0.0.1', PORT)
|
|
|
77
77
|
machine.listen(fd, 128)
|
|
78
78
|
|
|
79
79
|
puts "Listening on localhost:#{PORT}"
|
|
80
|
-
machine.accept_each(fd) { |
|
|
80
|
+
machine.accept_each(fd) { | io| machine.spin { handle_connection(machine, io) } }
|