uringmachine 0.30.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 +16 -4
- data/README.md +45 -38
- data/TODO.md +21 -4
- data/benchmark/bm_io_pipe.rb +2 -2
- data/benchmark/common.rb +16 -16
- data/benchmark/gets.rb +5 -5
- data/benchmark/gets_concurrent.rb +12 -12
- data/benchmark/http_parse.rb +14 -14
- data/benchmark/http_server_accept_queue.rb +11 -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 +50 -22
- data/docs/design/buffer_pool.md +1 -1
- data/examples/fiber_concurrency_io.rb +52 -0
- data/examples/fiber_concurrency_naive.rb +26 -0
- data/examples/fiber_concurrency_runqueue.rb +33 -0
- data/examples/fiber_concurrency_um.rb +16 -0
- data/examples/io_uring_simple.c +33 -0
- data/examples/pg.rb +2 -2
- data/examples/stream.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.c +20 -3
- data/ext/um/um.h +24 -18
- data/ext/um/um_ext.c +2 -4
- data/ext/um/um_io.c +775 -0
- data/ext/um/um_io_class.c +394 -0
- data/ext/um/um_ssl.c +37 -2
- data/ext/um/um_utils.c +1 -1
- data/grant-2025/final-report.md +2 -0
- data/grant-2025/journal.md +1 -1
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +18 -18
- data/test/{test_stream.rb → test_io.rb} +290 -153
- data/test/test_um.rb +18 -18
- metadata +17 -6
- data/ext/um/um_stream.c +0 -706
- data/ext/um/um_stream_class.c +0 -317
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
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# 0.32.0 2026-04-03
|
|
2
|
+
|
|
3
|
+
- Rename `UM::Connection` to `UM::IO`
|
|
4
|
+
|
|
5
|
+
# 0.31.0 2026-03-31
|
|
6
|
+
|
|
7
|
+
- Rework `Stream` into `Connection` class:
|
|
8
|
+
- Rename modes, improve SSL detection
|
|
9
|
+
- Rename and enhance different read methods
|
|
10
|
+
- Add `#write` method
|
|
11
|
+
- Add `#resp_write` method
|
|
12
|
+
|
|
1
13
|
# 0.30.0 2026-03-23
|
|
2
14
|
|
|
3
15
|
- Add `Stream#each`
|
|
@@ -30,7 +42,7 @@
|
|
|
30
42
|
|
|
31
43
|
# 0.28.2 2026-02-20
|
|
32
44
|
|
|
33
|
-
- Fix `Stream#
|
|
45
|
+
- Fix `Stream#read`
|
|
34
46
|
|
|
35
47
|
# 0.28.1 2026-02-20
|
|
36
48
|
|
|
@@ -169,9 +181,9 @@
|
|
|
169
181
|
|
|
170
182
|
# 2025-06-03 Version 0.12
|
|
171
183
|
|
|
172
|
-
- Add buffer, maxlen params to `Stream#
|
|
173
|
-
- Add buffer param to `Stream#
|
|
174
|
-
- Remove `Stream#
|
|
184
|
+
- Add buffer, maxlen params to `Stream#read_line`
|
|
185
|
+
- Add buffer param to `Stream#read`
|
|
186
|
+
- Remove `Stream#resp_read_line`, `Stream#resp_read` methods
|
|
175
187
|
|
|
176
188
|
# 2025-06-02 Version 0.11.1
|
|
177
189
|
|
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,64 +286,69 @@ fiber = Fiber.schedule do
|
|
|
286
286
|
end
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
##
|
|
289
|
+
## IO API
|
|
290
290
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
(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
|
-
(see also [
|
|
298
|
-
advantage of io_uring's
|
|
299
|
-
introduction of [incremental buffer
|
|
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)
|
|
335
|
+
|
|
336
|
+
# Read continuously until EOF
|
|
337
|
+
io.read_each { |data| ... }
|
|
336
338
|
|
|
337
339
|
# Skip 3 bytes:
|
|
338
|
-
|
|
340
|
+
io.skip(3)
|
|
341
|
+
|
|
342
|
+
# Write
|
|
343
|
+
io.write('foo', 'bar', 'baz')
|
|
339
344
|
```
|
|
340
345
|
|
|
341
346
|
Here's an example of a how a basic HTTP request parser might be implemented
|
|
342
|
-
using a
|
|
347
|
+
using a `UM::IO`:
|
|
343
348
|
|
|
344
349
|
```ruby
|
|
345
|
-
def parse_http_request_headers(
|
|
346
|
-
request_line =
|
|
350
|
+
def parse_http_request_headers(io)
|
|
351
|
+
request_line = io.read_line(0)
|
|
347
352
|
m = request_line.match(REQUEST_LINE_RE)
|
|
348
353
|
return nil if !m
|
|
349
354
|
|
|
@@ -354,7 +359,7 @@ def parse_http_request_headers(stream)
|
|
|
354
359
|
}
|
|
355
360
|
|
|
356
361
|
while true
|
|
357
|
-
line =
|
|
362
|
+
line = io.read_line(0)
|
|
358
363
|
break if !line || line.empty?
|
|
359
364
|
|
|
360
365
|
m = line.match(HEADER_RE)
|
|
@@ -364,24 +369,26 @@ def parse_http_request_headers(stream)
|
|
|
364
369
|
end
|
|
365
370
|
```
|
|
366
371
|
|
|
367
|
-
###
|
|
372
|
+
### IO modes
|
|
368
373
|
|
|
369
|
-
|
|
370
|
-
|
|
374
|
+
IO modes allow IOs to be transport agnostic. The following modes are currently
|
|
375
|
+
supported:
|
|
371
376
|
|
|
372
|
-
- `:
|
|
377
|
+
- `:fd` - use the buffer pool, read data using multishot read
|
|
373
378
|
(this is the default mode).
|
|
374
|
-
- `:
|
|
379
|
+
- `:socket` - use the buffer pool, read data using multishot recv.
|
|
375
380
|
- `:ssl` - read from an `SSLSocket` object.
|
|
376
381
|
|
|
377
|
-
The mode is specified as an additional argument to `
|
|
382
|
+
The mode is specified as an additional argument to `IO.new`:
|
|
378
383
|
|
|
379
384
|
```ruby
|
|
380
|
-
#
|
|
381
|
-
|
|
385
|
+
# using recv/send:
|
|
386
|
+
io = machine.io(fd, :socket)
|
|
382
387
|
|
|
383
|
-
#
|
|
384
|
-
|
|
388
|
+
# SSL I/O:
|
|
389
|
+
io = machine.io(ssl, :ssl)
|
|
390
|
+
# or simply:
|
|
391
|
+
io = machine.io(ssl)
|
|
385
392
|
```
|
|
386
393
|
|
|
387
394
|
## Performance
|
data/TODO.md
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
- Rename Connection to IO
|
|
2
|
+
|
|
3
|
+
```ruby
|
|
4
|
+
io = machine.io(fd)
|
|
5
|
+
l = io.read_line(4)
|
|
6
|
+
io.write('foo')
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
- Add `IO#fd`/`IO#target` method
|
|
10
|
+
|
|
11
|
+
- Add `UM#inspect` (show size, modes)
|
|
12
|
+
- Add `UM::IO#inspect` (show target, mode, pending bytes)
|
|
13
|
+
|
|
14
|
+
## Reimplement multishot read/recv using buffer pool
|
|
15
|
+
|
|
16
|
+
- remove `#setup_buffer_ring` method
|
|
17
|
+
- use buffer pool, just like UM::Connection
|
|
18
|
+
|
|
1
19
|
## immediate
|
|
2
20
|
|
|
3
21
|
- Add tests for support for Set in `machine#await`
|
|
4
22
|
- Add tests for support for Set, Array in `machine#join`
|
|
5
|
-
- Add
|
|
6
|
-
- Add
|
|
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`
|
|
7
26
|
|
|
8
27
|
## Balancing I/O with the runqueue
|
|
9
28
|
|
|
@@ -70,11 +89,9 @@
|
|
|
70
89
|
|
|
71
90
|
## ops still not implemented
|
|
72
91
|
|
|
73
|
-
- splice / - tee
|
|
74
92
|
- sendto
|
|
75
93
|
- recvfrom
|
|
76
94
|
- poll_multishot
|
|
77
|
-
- fsync
|
|
78
95
|
- mkdir / mkdirat
|
|
79
96
|
- link / linkat / unlink / unlinkat / symlink
|
|
80
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
44
|
x.report('IO#gets') { io_gets }
|
|
45
45
|
x.report('UM#read+buf') { um_read }
|
|
46
|
-
x.report('UM::
|
|
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
|
@@ -65,7 +65,7 @@ require 'stringio'
|
|
|
65
65
|
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/1\.1)/i
|
|
66
66
|
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
|
67
67
|
|
|
68
|
-
def
|
|
68
|
+
def read_line(fd, sio, buffer)
|
|
69
69
|
while true
|
|
70
70
|
line = sio.gets(chomp: true)
|
|
71
71
|
return line if line
|
|
@@ -76,7 +76,7 @@ def get_line(fd, sio, buffer)
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def get_request_line(fd, sio, buffer)
|
|
79
|
-
line =
|
|
79
|
+
line = read_line(fd, sio, buffer)
|
|
80
80
|
|
|
81
81
|
m = line.match(RE_REQUEST_LINE)
|
|
82
82
|
return nil if !m
|
|
@@ -96,7 +96,7 @@ def parse_headers(fd)
|
|
|
96
96
|
return nil if !headers
|
|
97
97
|
|
|
98
98
|
while true
|
|
99
|
-
line =
|
|
99
|
+
line = read_line(fd, sio, buffer)
|
|
100
100
|
break if line.empty?
|
|
101
101
|
|
|
102
102
|
m = line.match(RE_HEADER_LINE)
|
|
@@ -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 stream_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 stream_get_request_line(stream, 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,17 +51,21 @@ 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)
|
|
62
62
|
end
|
|
63
63
|
rescue InvalidHeadersError, SystemCallError => e
|
|
64
64
|
# ignore
|
|
65
|
+
rescue => e
|
|
66
|
+
p e
|
|
67
|
+
p e.backtrace
|
|
68
|
+
exit!
|
|
65
69
|
ensure
|
|
66
70
|
machine.close_async(fd)
|
|
67
71
|
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)
|
|
@@ -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) } }
|