uringmachine 0.29.0 → 0.29.2
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 +11 -0
- data/README.md +106 -6
- data/TODO.md +0 -1
- data/benchmark/gets_concurrent.rb +122 -0
- data/docs/design/buffer_pool.md +1 -1
- data/ext/um/um.c +8 -3
- data/ext/um/um.h +7 -8
- data/ext/um/um_buffer_pool.c +8 -6
- data/ext/um/um_class.c +4 -4
- data/ext/um/um_stream.c +71 -64
- data/ext/um/um_stream_class.c +112 -0
- data/grant-2025/tasks.md +3 -2
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +9 -0
- data/test/helper.rb +2 -0
- data/test/test_fiber_scheduler.rb +3 -3
- data/test/test_stream.rb +74 -11
- data/test/test_um.rb +66 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6151ac8411facd0ef37fa66a7cae623d2137f335bbe1e178529cb0a76c9f56c3
|
|
4
|
+
data.tar.gz: 5918fde9ea4ba936760a3d402207bd6fd5806337b005cd3633ab1cf0f8584ed0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d77a7f37251930db32ec8023b1cb60e5595348544b279b175d39a528dbe782a703c75f6ebde5b2dd84379ec12ae83decf47f11f0b692e4975a36f146b4bc975
|
|
7
|
+
data.tar.gz: 190d3d88d32f91241c3c9ed3fc674d5318fcf630e64d5d5fdecdc7a86b6e62f03786190a021f4828e6a40022ccbf301a3cffdf0311ff35bc747889d9f00fbb9b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
# 0.29.2 2026-03-15
|
|
2
|
+
|
|
3
|
+
- Add `Stream#consumed`, `Stream#pending`
|
|
4
|
+
- Add `UM#stream`
|
|
5
|
+
- Add `Stream#skip`
|
|
6
|
+
|
|
7
|
+
# 0.29.1 2026-03-14
|
|
8
|
+
|
|
9
|
+
- Add support for exception instance in `#timeout`
|
|
10
|
+
- Improve buffer commit level handling
|
|
11
|
+
|
|
1
12
|
# 0.29.0 2026-03-11
|
|
2
13
|
|
|
3
14
|
- Reimplement streams based on buffer pool
|
data/README.md
CHANGED
|
@@ -32,11 +32,13 @@ implementation that allows integration with the entire Ruby ecosystem.
|
|
|
32
32
|
## Features
|
|
33
33
|
|
|
34
34
|
- Automatic fiber switching when performing blocking I/O operations.
|
|
35
|
-
- Automatic cancellation
|
|
35
|
+
- Automatic cancellation ongoing operations with Ruby exceptions.
|
|
36
36
|
- General-purpose API for cancelling any operation on timeout.
|
|
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
|
+
- Read streams with automatic buffer management.
|
|
41
|
+
- Optimized I/O for encrypted SSL connections.
|
|
40
42
|
|
|
41
43
|
## Design
|
|
42
44
|
|
|
@@ -61,8 +63,8 @@ allow any library that performs I/O using standard-library classes such as `IO`,
|
|
|
61
63
|
|
|
62
64
|
To install UringMachine, simply run `gem install uringmachine` or `bundle add
|
|
63
65
|
uringmachine` in your project directory. Note: to use UringMachine, you'll need
|
|
64
|
-
a Linux machine with a minimum kernel version of 6.7. Some features
|
|
65
|
-
newer kernel versions.
|
|
66
|
+
a Linux machine with a minimum Linux kernel version of 6.7. Some features
|
|
67
|
+
require newer kernel versions.
|
|
66
68
|
|
|
67
69
|
To perform I/O using UringMachine, simply create an instance:
|
|
68
70
|
|
|
@@ -79,7 +81,7 @@ You can perform I/O by directly making method calls such as `write` or `read`
|
|
|
79
81
|
```ruby
|
|
80
82
|
# Most UringMachine instance methods will need you to provide a file descriptor.
|
|
81
83
|
# Here we print a message to STDOUT. Note the explicit line break:
|
|
82
|
-
machine.write(
|
|
84
|
+
machine.write(UM::STDOUT_FILENO, "Hello, world!\n")
|
|
83
85
|
```
|
|
84
86
|
|
|
85
87
|
UringMachine provides an I/O interface that is to a large degree equivalent to
|
|
@@ -91,14 +93,14 @@ the Unix standard C interface:
|
|
|
91
93
|
fd = machine.open('foo.txt', UM::O_RDONLY)
|
|
92
94
|
buf = +''
|
|
93
95
|
size = machine.read(fd, buf, 8192)
|
|
94
|
-
machine.write(
|
|
96
|
+
machine.write(UM::STDOUT_FILENO, "File content: #{buf.inspect}")
|
|
95
97
|
machine.close(fd)
|
|
96
98
|
|
|
97
99
|
# Or alternatively (with automatic file closing):
|
|
98
100
|
machine.open('foo.txt', UM::O_RDONLY) do |fd|
|
|
99
101
|
buf = +''
|
|
100
102
|
size = machine.read(fd, buf, 8192)
|
|
101
|
-
machine.write(
|
|
103
|
+
machine.write(UM::STDOUT_FILENO, "File content: #{buf.inspect}")
|
|
102
104
|
end
|
|
103
105
|
```
|
|
104
106
|
|
|
@@ -284,6 +286,104 @@ fiber = Fiber.schedule do
|
|
|
284
286
|
end
|
|
285
287
|
```
|
|
286
288
|
|
|
289
|
+
## Read Streams
|
|
290
|
+
|
|
291
|
+
A UringMachine stream is used to efficiently read from a socket or other file
|
|
292
|
+
descriptor. Streams are ideal for implementing the read side of protocols, and
|
|
293
|
+
provide an API that is useful for both line-based protocols and binary
|
|
294
|
+
(frame-based) protocols.
|
|
295
|
+
|
|
296
|
+
A stream is associated with a UringMachine instance and a target file descriptor
|
|
297
|
+
(see also [stream modes](#stream-modes) below). Behind the scenes, streams take
|
|
298
|
+
advantage of io_uring's registered buffers feature, and more recently, the
|
|
299
|
+
introduction of [incremental buffer
|
|
300
|
+
consumption](https://github.com/axboe/liburing/wiki/What's-new-with-io_uring-in-6.11-and-6.12#incremental-provided-buffer-consumption).
|
|
301
|
+
|
|
302
|
+
When streams are used, UringMachine automatically manages the buffers it
|
|
303
|
+
provides to the kernel, maximizing buffer reuse and minimizing allocations.
|
|
304
|
+
UringMachine also responds to stress conditions (increased incoming traffic) by
|
|
305
|
+
automatically provisioning additional buffers.
|
|
306
|
+
|
|
307
|
+
To create a stream for a given fd, use `UM#stream`:
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
stream = machine.stream(fd)
|
|
311
|
+
|
|
312
|
+
# you can also provide a block that will be passed the stream instance:
|
|
313
|
+
machine.stream(fd) { |s| do_something_with(s) }
|
|
314
|
+
|
|
315
|
+
# you can also instantiate a stream directly:
|
|
316
|
+
stream = UM::Stream.new(machine, fd)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The following API is used to interact with the stream:
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
# Read until a newline character is encountered:
|
|
323
|
+
line = stream.get_line(0)
|
|
324
|
+
|
|
325
|
+
# Read line with a maximum length of 13 bytes:
|
|
326
|
+
line = stream.get_line(13)
|
|
327
|
+
|
|
328
|
+
# Read all data:
|
|
329
|
+
buf = stream.get_string(0)
|
|
330
|
+
|
|
331
|
+
# Read exactly 13 bytes:
|
|
332
|
+
buf = stream.get_string(13)
|
|
333
|
+
|
|
334
|
+
# Read up to 13 bytes:
|
|
335
|
+
buf = stream.get_string(-13)
|
|
336
|
+
|
|
337
|
+
# Skip 3 bytes:
|
|
338
|
+
stream.skip(3)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Here's an example of a how a basic HTTP request parser might be implemented
|
|
342
|
+
using a stream:
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
def parse_http_request_headers(stream)
|
|
346
|
+
request_line = stream.get_line(0)
|
|
347
|
+
m = request_line.match(REQUEST_LINE_RE)
|
|
348
|
+
return nil if !m
|
|
349
|
+
|
|
350
|
+
headers = {
|
|
351
|
+
':method' => m[1],
|
|
352
|
+
':path' => m[2],
|
|
353
|
+
':protocol' => m[3]
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
while true
|
|
357
|
+
line = stream.get_line(0)
|
|
358
|
+
break if !line || line.empty?
|
|
359
|
+
|
|
360
|
+
m = line.match(HEADER_RE)
|
|
361
|
+
headers[m[1].downcase] = m[2]
|
|
362
|
+
end
|
|
363
|
+
headers
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Stream modes
|
|
368
|
+
|
|
369
|
+
Stream modes allow streams to be transport agnostic. Currently streams support
|
|
370
|
+
three modes:
|
|
371
|
+
|
|
372
|
+
- `:bp_read` - use the buffer pool, read data using multishot read
|
|
373
|
+
(this is the default mode).
|
|
374
|
+
- `:bp_recv` - use the buffer pool, read data using multishot recv.
|
|
375
|
+
- `:ssl` - read from an `SSLSocket` object.
|
|
376
|
+
|
|
377
|
+
The mode is specified as an additional argument to `Stream.new`:
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
# stream using recv:
|
|
381
|
+
stream = machine.stream(fd, :bp_recv)
|
|
382
|
+
|
|
383
|
+
# stream on an SSL socket:
|
|
384
|
+
stream = machine.stream(ssl, :ssl)
|
|
385
|
+
```
|
|
386
|
+
|
|
287
387
|
## Performance
|
|
288
388
|
|
|
289
389
|
[Detailed benchmarks](benchmark/README.md)
|
data/TODO.md
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/inline'
|
|
4
|
+
|
|
5
|
+
gemfile do
|
|
6
|
+
source 'https://rubygems.org'
|
|
7
|
+
gem 'uringmachine', path: '..'
|
|
8
|
+
gem 'benchmark'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require 'benchmark'
|
|
12
|
+
require 'uringmachine'
|
|
13
|
+
|
|
14
|
+
C = 10
|
|
15
|
+
N = 1000
|
|
16
|
+
|
|
17
|
+
CMD = <<~EOF
|
|
18
|
+
bash -c "for i in {1..#{C*2}}; do nc -l -p 1234 </dev/random & done; wait $(jobs -p)"
|
|
19
|
+
EOF
|
|
20
|
+
|
|
21
|
+
def start_server
|
|
22
|
+
@pid = fork {
|
|
23
|
+
p :server_launch
|
|
24
|
+
`#{CMD}`
|
|
25
|
+
puts
|
|
26
|
+
p :server_done
|
|
27
|
+
puts
|
|
28
|
+
}
|
|
29
|
+
sleep(0.5)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def stop_server
|
|
33
|
+
Process.kill('SIGINT', @pid)
|
|
34
|
+
Process.wait(@pid)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def io_gets
|
|
38
|
+
start_server
|
|
39
|
+
tt = C.times.map {
|
|
40
|
+
Thread.new do
|
|
41
|
+
s = TCPSocket.new('localhost', 1234)
|
|
42
|
+
# io = File.open('/dev/random', 'r')
|
|
43
|
+
N.times { s.gets }
|
|
44
|
+
ensure
|
|
45
|
+
s.close
|
|
46
|
+
end
|
|
47
|
+
}
|
|
48
|
+
tt.each(&:join)
|
|
49
|
+
ensure
|
|
50
|
+
stop_server
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@machine = UM.new
|
|
54
|
+
|
|
55
|
+
def buf_gets(fd, buffer)
|
|
56
|
+
while true
|
|
57
|
+
idx = buffer.byteindex("\n")
|
|
58
|
+
if idx
|
|
59
|
+
line = buffer[0..(idx - 1)]
|
|
60
|
+
|
|
61
|
+
buffer = buffer[(idx + 1)..-1]
|
|
62
|
+
return line
|
|
63
|
+
end
|
|
64
|
+
@machine.read(fd, buffer, 65536, -1)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def um_read
|
|
69
|
+
start_server
|
|
70
|
+
ff = C.times.map {
|
|
71
|
+
@machine.spin do
|
|
72
|
+
# fd = @machine.open('/dev/random', UM::O_RDONLY)
|
|
73
|
+
fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
74
|
+
@machine.connect(fd, '127.0.0.1', 1234)
|
|
75
|
+
buffer = +''.encode(Encoding::US_ASCII)
|
|
76
|
+
N.times { buf_gets(fd, buffer) }
|
|
77
|
+
ensure
|
|
78
|
+
@machine.close(fd)
|
|
79
|
+
end
|
|
80
|
+
}
|
|
81
|
+
@machine.await(ff)
|
|
82
|
+
ensure
|
|
83
|
+
stop_server
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@total_stream = 0
|
|
87
|
+
def um_stream_do
|
|
88
|
+
# fd = @machine.open('/dev/random', UM::O_RDONLY)
|
|
89
|
+
fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
90
|
+
@machine.connect(fd, '127.0.0.1', 1234)
|
|
91
|
+
stream = UM::Stream.new(@machine, fd)
|
|
92
|
+
N.times { @total_stream += stream.get_line(0)&.bytesize || 0 }
|
|
93
|
+
rescue => e
|
|
94
|
+
p e
|
|
95
|
+
p e.backtrace
|
|
96
|
+
ensure
|
|
97
|
+
stream.clear
|
|
98
|
+
@machine.close(fd)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def um_stream
|
|
102
|
+
start_server
|
|
103
|
+
ff = C.times.map {
|
|
104
|
+
@machine.snooze
|
|
105
|
+
@machine.spin { um_stream_do }
|
|
106
|
+
}
|
|
107
|
+
@machine.await(ff)
|
|
108
|
+
pp total: @total_stream
|
|
109
|
+
ensure
|
|
110
|
+
stop_server
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
p(C:, N:)
|
|
114
|
+
um_stream
|
|
115
|
+
pp @machine.metrics
|
|
116
|
+
exit
|
|
117
|
+
|
|
118
|
+
Benchmark.bm do
|
|
119
|
+
it.report('Thread/IO#gets') { io_gets }
|
|
120
|
+
it.report('Fiber/UM#read+buf') { um_read }
|
|
121
|
+
it.report('Fiber/UM::Stream') { um_stream }
|
|
122
|
+
end
|
data/docs/design/buffer_pool.md
CHANGED
data/ext/um/um.c
CHANGED
|
@@ -540,15 +540,19 @@ VALUE um_timeout_complete(VALUE arg) {
|
|
|
540
540
|
return Qnil;
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
VALUE um_timeout(struct um *machine, VALUE interval, VALUE
|
|
543
|
+
VALUE um_timeout(struct um *machine, VALUE interval, VALUE obj) {
|
|
544
544
|
static ID ID_new = 0;
|
|
545
|
-
|
|
545
|
+
|
|
546
|
+
if (TYPE(obj) == T_CLASS) {
|
|
547
|
+
if (unlikely(!ID_new)) ID_new = rb_intern("new");
|
|
548
|
+
obj = rb_funcall(obj, ID_new, 0);
|
|
549
|
+
}
|
|
546
550
|
|
|
547
551
|
struct um_op *op = um_op_acquire(machine);
|
|
548
552
|
um_prep_op(machine, op, OP_TIMEOUT, 2, 0);
|
|
549
553
|
op->ts = um_double_to_timespec(NUM2DBL(interval));
|
|
550
554
|
RB_OBJ_WRITE(machine->self, &op->fiber, rb_fiber_current());
|
|
551
|
-
RB_OBJ_WRITE(machine->self, &op->value,
|
|
555
|
+
RB_OBJ_WRITE(machine->self, &op->value, obj);
|
|
552
556
|
RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
|
|
553
557
|
|
|
554
558
|
struct io_uring_sqe *sqe = um_get_sqe(machine, op);
|
|
@@ -556,6 +560,7 @@ VALUE um_timeout(struct um *machine, VALUE interval, VALUE class) {
|
|
|
556
560
|
|
|
557
561
|
struct op_ctx ctx = { .machine = machine, .op = op };
|
|
558
562
|
return rb_ensure(rb_yield, Qnil, um_timeout_complete, (VALUE)&ctx);
|
|
563
|
+
RB_GC_GUARD(obj);
|
|
559
564
|
}
|
|
560
565
|
|
|
561
566
|
/*******************************************************************************
|
data/ext/um/um.h
CHANGED
|
@@ -111,12 +111,10 @@ enum um_stream_mode {
|
|
|
111
111
|
|
|
112
112
|
#define BP_BGID 0xF00B
|
|
113
113
|
#define BP_BR_ENTRIES 1024
|
|
114
|
-
|
|
115
114
|
#define BP_INITIAL_BUFFER_SIZE (1U << 14) // 16KB
|
|
116
|
-
#define
|
|
115
|
+
#define BP_INITIAL_COMMIT_LEVEL (BP_INITIAL_BUFFER_SIZE * 16) // 256KB
|
|
117
116
|
#define BP_MAX_BUFFER_SIZE (1U << 20) // 1MB
|
|
118
|
-
|
|
119
|
-
#define BP_MAX_COMMIT_THRESHOLD (BP_MAX_BUFFER_SIZE * BP_BR_ENTRIES) // 1GB
|
|
117
|
+
#define BP_MAX_COMMIT_LEVEL (BP_MAX_BUFFER_SIZE * BP_BR_ENTRIES) // 1GB
|
|
120
118
|
#define BP_AVAIL_BID_BITMAP_WORDS (BP_BR_ENTRIES / 64)
|
|
121
119
|
|
|
122
120
|
struct um_buffer {
|
|
@@ -163,7 +161,7 @@ struct um_op {
|
|
|
163
161
|
struct iovec *iovecs; // used for vectorized write/send
|
|
164
162
|
siginfo_t siginfo; // used for waitid
|
|
165
163
|
int int_value; // used for getsockopt
|
|
166
|
-
size_t
|
|
164
|
+
size_t bp_commit_level; // buffer pool commit threshold
|
|
167
165
|
};
|
|
168
166
|
};
|
|
169
167
|
|
|
@@ -234,7 +232,7 @@ struct um {
|
|
|
234
232
|
|
|
235
233
|
struct io_uring_buf_ring *bp_br;
|
|
236
234
|
size_t bp_buffer_size;
|
|
237
|
-
size_t
|
|
235
|
+
size_t bp_commit_level;
|
|
238
236
|
struct um_buffer **bp_commited_buffers;
|
|
239
237
|
uint64_t bp_avail_bid_bitmap[BP_AVAIL_BID_BITMAP_WORDS];
|
|
240
238
|
|
|
@@ -285,7 +283,8 @@ struct um_stream {
|
|
|
285
283
|
struct um_op *op;
|
|
286
284
|
struct um_segment *head;
|
|
287
285
|
struct um_segment *tail;
|
|
288
|
-
size_t
|
|
286
|
+
size_t consumed_bytes;
|
|
287
|
+
size_t pending_bytes;
|
|
289
288
|
size_t pos;
|
|
290
289
|
int eof;
|
|
291
290
|
};
|
|
@@ -437,7 +436,7 @@ void stream_teardown(struct um_stream *stream);
|
|
|
437
436
|
void stream_clear(struct um_stream *stream);
|
|
438
437
|
VALUE stream_get_line(struct um_stream *stream, VALUE buf, size_t maxlen);
|
|
439
438
|
VALUE stream_get_string(struct um_stream *stream, VALUE out_buffer, ssize_t len, size_t inc, int safe_inc);
|
|
440
|
-
|
|
439
|
+
void stream_skip(struct um_stream *stream, size_t inc, int safe_inc);
|
|
441
440
|
VALUE resp_decode(struct um_stream *stream, VALUE out_buffer);
|
|
442
441
|
void resp_encode(struct um_write_buffer *buf, VALUE obj);
|
|
443
442
|
void resp_encode_cmd(struct um_write_buffer *buf, int argc, VALUE *argv);
|
data/ext/um/um_buffer_pool.c
CHANGED
|
@@ -31,7 +31,6 @@ inline struct um_buffer *bp_buffer_checkout(struct um *machine) {
|
|
|
31
31
|
buffer->ref_count++;
|
|
32
32
|
buffer->pos = 0;
|
|
33
33
|
buffer->next = NULL;
|
|
34
|
-
|
|
35
34
|
return buffer;
|
|
36
35
|
}
|
|
37
36
|
|
|
@@ -76,7 +75,7 @@ inline void bp_setup(struct um *machine) {
|
|
|
76
75
|
if (unlikely(!machine->bp_br)) rb_syserr_fail(ret, strerror(ret));
|
|
77
76
|
|
|
78
77
|
machine->bp_buffer_size = BP_INITIAL_BUFFER_SIZE;
|
|
79
|
-
machine->
|
|
78
|
+
machine->bp_commit_level = BP_INITIAL_COMMIT_LEVEL;
|
|
80
79
|
machine->bp_commited_buffers = malloc(sizeof(struct um_buffer) * BP_BR_ENTRIES);
|
|
81
80
|
memset(machine->bp_commited_buffers, 0, sizeof(struct um_buffer) * BP_BR_ENTRIES);
|
|
82
81
|
memset(machine->bp_avail_bid_bitmap, 0xFF, sizeof(machine->bp_avail_bid_bitmap));
|
|
@@ -160,10 +159,13 @@ inline struct um_buffer *get_buffer(struct um *machine, int bid) {
|
|
|
160
159
|
|
|
161
160
|
inline int should_commit_more_p(struct um *machine) {
|
|
162
161
|
return (machine->bp_buffer_count < BP_BR_ENTRIES) &&
|
|
163
|
-
(machine->bp_total_commited < machine->
|
|
162
|
+
(machine->bp_total_commited < machine->bp_commit_level);
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
inline void bp_ensure_commit_level(struct um *machine) {
|
|
166
|
+
if (machine->bp_total_commited > (machine->bp_commit_level / 2))
|
|
167
|
+
return;
|
|
168
|
+
|
|
167
169
|
int added = 0;
|
|
168
170
|
while (should_commit_more_p(machine)) {
|
|
169
171
|
if (likely(commit_buffer(machine, added))) added++;
|
|
@@ -178,11 +180,11 @@ inline void bp_ensure_commit_level(struct um *machine) {
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
inline void bp_handle_enobufs(struct um *machine) {
|
|
181
|
-
if (unlikely(machine->
|
|
183
|
+
if (unlikely(machine->bp_commit_level >= BP_MAX_COMMIT_LEVEL))
|
|
182
184
|
rb_raise(eUMError, "Buffer starvation");
|
|
183
185
|
|
|
184
|
-
machine->
|
|
185
|
-
while (machine->bp_buffer_size < machine->
|
|
186
|
+
machine->bp_commit_level *= 2;
|
|
187
|
+
while (machine->bp_buffer_size < machine->bp_commit_level / 4)
|
|
186
188
|
machine->bp_buffer_size *= 2;
|
|
187
189
|
bp_discard_buffer_freelist(machine);
|
|
188
190
|
}
|
data/ext/um/um_class.c
CHANGED
|
@@ -334,17 +334,17 @@ VALUE UM_schedule(VALUE self, VALUE fiber, VALUE value) {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
/* Runs the given block, interrupting its execution if its runtime exceeds the
|
|
337
|
-
* given timeout interval (in seconds).
|
|
337
|
+
* given timeout interval (in seconds), raising the specified exception.
|
|
338
338
|
*
|
|
339
339
|
* - https://www.man7.org/linux/man-pages//man3/io_uring_prep_timeoute.3.html
|
|
340
340
|
*
|
|
341
341
|
* @param interval [Number] timeout interval in seconds
|
|
342
|
-
* @param
|
|
342
|
+
* @param exception [any] timeout exception class or instance
|
|
343
343
|
* @return [any] block's return value
|
|
344
344
|
*/
|
|
345
|
-
VALUE UM_timeout(VALUE self, VALUE interval, VALUE
|
|
345
|
+
VALUE UM_timeout(VALUE self, VALUE interval, VALUE exception) {
|
|
346
346
|
struct um *machine = um_get_machine(self);
|
|
347
|
-
return um_timeout(machine, interval,
|
|
347
|
+
return um_timeout(machine, interval, exception);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
/* Puts the current fiber to sleep for the given time duration (in seconds),
|
data/ext/um/um_stream.c
CHANGED
|
@@ -9,7 +9,7 @@ inline void stream_add_segment(struct um_stream *stream, struct um_segment *segm
|
|
|
9
9
|
}
|
|
10
10
|
else
|
|
11
11
|
stream->head = stream->tail = segment;
|
|
12
|
-
stream->
|
|
12
|
+
stream->pending_bytes += segment->len;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
inline int stream_process_op_result(struct um_stream *stream, struct um_op_result *result) {
|
|
@@ -50,7 +50,7 @@ void stream_multishot_op_start(struct um_stream *stream) {
|
|
|
50
50
|
default:
|
|
51
51
|
um_raise_internal_error("Invalid multishot op");
|
|
52
52
|
}
|
|
53
|
-
stream->op->
|
|
53
|
+
stream->op->bp_commit_level = stream->machine->bp_commit_level;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
void stream_multishot_op_stop(struct um_stream *stream) {
|
|
@@ -73,7 +73,7 @@ void um_stream_cleanup(struct um_stream *stream) {
|
|
|
73
73
|
um_segment_checkin(stream->machine, stream->head);
|
|
74
74
|
stream->head = next;
|
|
75
75
|
}
|
|
76
|
-
stream->
|
|
76
|
+
stream->pending_bytes = 0;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// returns true if case of ENOBUFS error, sets more to true if more data forthcoming
|
|
@@ -124,7 +124,7 @@ void stream_clear(struct um_stream *stream) {
|
|
|
124
124
|
um_segment_checkin(stream->machine, stream->head);
|
|
125
125
|
stream->head = next;
|
|
126
126
|
}
|
|
127
|
-
stream->
|
|
127
|
+
stream->pending_bytes = 0;
|
|
128
128
|
|
|
129
129
|
if (stream->working_buffer) {
|
|
130
130
|
bp_buffer_checkin(stream->machine, stream->working_buffer);
|
|
@@ -156,16 +156,27 @@ int stream_get_more_segments_bp(struct um_stream *stream) {
|
|
|
156
156
|
enobufs = stream_process_segments(stream, &total_bytes, &more);
|
|
157
157
|
um_op_multishot_results_clear(stream->machine, stream->op);
|
|
158
158
|
if (unlikely(enobufs)) {
|
|
159
|
-
int should_restart = stream->
|
|
159
|
+
int should_restart = stream->pending_bytes < (stream->machine->bp_buffer_size * 4);
|
|
160
|
+
// int same_threshold = stream->op->bp_commit_level == stream->machine->bp_commit_level;
|
|
161
|
+
|
|
162
|
+
// fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
|
|
163
|
+
// stream,
|
|
164
|
+
// total_bytes, stream->pending_bytes, stream->machine->bp_commit_level,
|
|
165
|
+
// stream->machine->bp_buffer_count,
|
|
166
|
+
// same_threshold, should_restart
|
|
167
|
+
// );
|
|
160
168
|
|
|
161
169
|
// If multiple stream ops are happening at the same time, they'll all get
|
|
162
170
|
// ENOBUFS! We track the commit threshold in the op in order to prevent
|
|
163
171
|
// running bp_handle_enobufs() more than once.
|
|
164
172
|
|
|
165
173
|
if (should_restart) {
|
|
166
|
-
if (stream->op->
|
|
174
|
+
if (stream->op->bp_commit_level == stream->machine->bp_commit_level)
|
|
167
175
|
bp_handle_enobufs(stream->machine);
|
|
168
|
-
|
|
176
|
+
|
|
177
|
+
um_op_release(stream->machine, stream->op);
|
|
178
|
+
stream->op = NULL;
|
|
179
|
+
// stream_multishot_op_start(stream);
|
|
169
180
|
}
|
|
170
181
|
else {
|
|
171
182
|
um_op_release(stream->machine, stream->op);
|
|
@@ -215,6 +226,49 @@ int stream_get_more_segments(struct um_stream *stream) {
|
|
|
215
226
|
|
|
216
227
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
217
228
|
|
|
229
|
+
inline void stream_shift_head(struct um_stream *stream) {
|
|
230
|
+
struct um_segment *consumed = stream->head;
|
|
231
|
+
stream->head = consumed->next;
|
|
232
|
+
if (!stream->head) stream->tail = NULL;
|
|
233
|
+
um_segment_checkin(stream->machine, consumed);
|
|
234
|
+
stream->pos = 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
inline void stream_skip(struct um_stream *stream, size_t inc, int safe_inc) {
|
|
238
|
+
if (unlikely(stream->eof && !stream->head)) return;
|
|
239
|
+
if (safe_inc && !stream->tail && !stream_get_more_segments(stream)) return;
|
|
240
|
+
|
|
241
|
+
while (inc) {
|
|
242
|
+
size_t segment_len = stream->head->len - stream->pos;
|
|
243
|
+
size_t inc_len = (segment_len <= inc) ? segment_len : inc;
|
|
244
|
+
inc -= inc_len;
|
|
245
|
+
stream->pos += inc_len;
|
|
246
|
+
stream->consumed_bytes += inc_len;
|
|
247
|
+
stream->pending_bytes -= inc_len;
|
|
248
|
+
if (stream->pos == stream->head->len) {
|
|
249
|
+
stream_shift_head(stream);
|
|
250
|
+
if (inc && safe_inc && !stream->head) {
|
|
251
|
+
if (!stream_get_more_segments(stream)) break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
inline void stream_copy(struct um_stream *stream, char *dest, size_t len) {
|
|
258
|
+
while (len) {
|
|
259
|
+
char *segment_ptr = stream->head->ptr + stream->pos;
|
|
260
|
+
size_t segment_len = stream->head->len - stream->pos;
|
|
261
|
+
size_t cpy_len = (segment_len <= len) ? segment_len : len;
|
|
262
|
+
memcpy(dest, segment_ptr, cpy_len);
|
|
263
|
+
|
|
264
|
+
len -= cpy_len;
|
|
265
|
+
stream->pos += cpy_len;
|
|
266
|
+
stream->consumed_bytes += cpy_len;
|
|
267
|
+
stream->pending_bytes -= cpy_len;
|
|
268
|
+
dest += cpy_len;
|
|
269
|
+
if (stream->pos == stream->head->len) stream_shift_head(stream);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
218
272
|
|
|
219
273
|
VALUE stream_consume_string(struct um_stream *stream, VALUE out_buffer, size_t len, size_t inc, int safe_inc) {
|
|
220
274
|
VALUE str = Qnil;
|
|
@@ -228,69 +282,22 @@ VALUE stream_consume_string(struct um_stream *stream, VALUE out_buffer, size_t l
|
|
|
228
282
|
}
|
|
229
283
|
else
|
|
230
284
|
str = rb_str_new(NULL, len);
|
|
231
|
-
char *
|
|
232
|
-
while (len) {
|
|
233
|
-
char *segment_ptr = stream->head->ptr + stream->pos;
|
|
234
|
-
size_t segment_len = stream->head->len - stream->pos;
|
|
235
|
-
size_t cpy_len = (segment_len <= len) ? segment_len : len;
|
|
236
|
-
memcpy(str_ptr, segment_ptr, cpy_len);
|
|
237
|
-
|
|
238
|
-
len -= cpy_len;
|
|
239
|
-
stream->pos += cpy_len;
|
|
240
|
-
stream->pending_len -= cpy_len;
|
|
241
|
-
str_ptr += cpy_len;
|
|
242
|
-
if (stream->pos == stream->head->len) {
|
|
243
|
-
struct um_segment *consumed = stream->head;
|
|
244
|
-
stream->head = consumed->next;
|
|
245
|
-
if (!stream->head) stream->tail = NULL;
|
|
246
|
-
um_segment_checkin(stream->machine, consumed);
|
|
247
|
-
stream->pos = 0;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
285
|
+
char *dest = RSTRING_PTR(str);
|
|
250
286
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
size_t inc_len = (segment_len <= inc) ? segment_len : inc;
|
|
254
|
-
inc -= inc_len;
|
|
255
|
-
stream->pos += inc_len;
|
|
256
|
-
stream->pending_len -= inc_len;
|
|
257
|
-
if (stream->pos == stream->head->len) {
|
|
258
|
-
struct um_segment *consumed = stream->head;
|
|
259
|
-
stream->head = consumed->next;
|
|
260
|
-
um_segment_checkin(stream->machine, consumed);
|
|
261
|
-
if (!stream->head) {
|
|
262
|
-
stream->tail = NULL;
|
|
263
|
-
if (inc && safe_inc) {
|
|
264
|
-
if (!stream_get_more_segments(stream)) break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
stream->pos = 0;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
287
|
+
stream_copy(stream, dest, len);
|
|
288
|
+
stream_skip(stream, inc, safe_inc);
|
|
270
289
|
return str;
|
|
271
290
|
RB_GC_GUARD(str);
|
|
272
291
|
}
|
|
273
292
|
|
|
274
|
-
// inline void stream_advance(struct um_stream *stream, size_t inc) {
|
|
275
|
-
// while (inc) {
|
|
276
|
-
// size_t segment_len = stream->head->len - stream->pos;
|
|
277
|
-
// size_t inc_len = (segment_len <= inc) ? segment_len : inc;
|
|
278
|
-
// inc -= inc_len;
|
|
279
|
-
// stream->pos += inc_len;
|
|
280
|
-
// if (stream->pos == stream->head->len) {
|
|
281
|
-
// struct um_segment *consumed = stream->head;
|
|
282
|
-
// stream->head = consumed->next;
|
|
283
|
-
// um_segment_checkin(stream->machine, consumed);
|
|
284
|
-
// if (!stream->head) {
|
|
285
|
-
// stream->tail = NULL;
|
|
286
|
-
// if (!stream_get_more_segments(stream)) return;
|
|
287
|
-
// }
|
|
288
|
-
// stream->pos = 0;
|
|
289
|
-
// }
|
|
290
|
-
// }
|
|
291
|
-
// }
|
|
292
|
-
|
|
293
293
|
VALUE stream_get_line(struct um_stream *stream, VALUE out_buffer, size_t maxlen) {
|
|
294
|
+
// if (stream->head) {
|
|
295
|
+
// fprintf(stderr, "head: %p pos: %ld: %.*s\n",
|
|
296
|
+
// stream->head, stream->pos,
|
|
297
|
+
// (int)(stream->head->len - stream->pos), stream->head->ptr + stream->pos
|
|
298
|
+
// );
|
|
299
|
+
// }
|
|
300
|
+
|
|
294
301
|
if (unlikely(stream->eof && !stream->head)) return Qnil;
|
|
295
302
|
if (!stream->tail && !stream_get_more_segments(stream)) return Qnil;
|
|
296
303
|
|
data/ext/um/um_stream_class.c
CHANGED
|
@@ -102,6 +102,20 @@ static inline void stream_setup(struct um_stream *stream, VALUE target, VALUE mo
|
|
|
102
102
|
rb_raise(eUMError, "Invalid stream mode");
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/* call-seq:
|
|
106
|
+
* UM::Stream.new(machine, fd, mode = nil) -> stream
|
|
107
|
+
* machine.stream(fd, mode = nil) -> stream
|
|
108
|
+
* machine.stream(fd, mode = nil) { |stream| ... }
|
|
109
|
+
*
|
|
110
|
+
* Initializes a new stream with the given UringMachine instance, target and
|
|
111
|
+
* optional mode. The target maybe a file descriptor, or an instance of
|
|
112
|
+
* OpenSSL::SSL::SSLSocket. In case of an SSL socket, the mode should be :ssl.
|
|
113
|
+
*
|
|
114
|
+
* @param machine [UringMachine] UringMachine instance
|
|
115
|
+
* @param target [integer, OpenSSL::SSL::SSLSocket] stream target: file descriptor or SSL socket
|
|
116
|
+
* @param mode [Symbol] optional stream mode: :bp_read, :bp_recv, :ssl
|
|
117
|
+
* @return [void]
|
|
118
|
+
*/
|
|
105
119
|
VALUE Stream_initialize(int argc, VALUE *argv, VALUE self) {
|
|
106
120
|
VALUE machine;
|
|
107
121
|
VALUE target;
|
|
@@ -118,6 +132,13 @@ VALUE Stream_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
118
132
|
return self;
|
|
119
133
|
}
|
|
120
134
|
|
|
135
|
+
/* call-seq:
|
|
136
|
+
* stream.mode -> mode
|
|
137
|
+
*
|
|
138
|
+
* Returns the stream mode.
|
|
139
|
+
*
|
|
140
|
+
* @return [Symbol] stream mode
|
|
141
|
+
*/
|
|
121
142
|
VALUE Stream_mode(VALUE self) {
|
|
122
143
|
struct um_stream *stream = um_get_stream(self);
|
|
123
144
|
switch (stream->mode) {
|
|
@@ -129,16 +150,57 @@ VALUE Stream_mode(VALUE self) {
|
|
|
129
150
|
return Qnil;
|
|
130
151
|
}
|
|
131
152
|
|
|
153
|
+
/* call-seq:
|
|
154
|
+
* stream.get_line(limit) -> str
|
|
155
|
+
*
|
|
156
|
+
* Reads from the string until a newline character is encountered. Returns the
|
|
157
|
+
* line without the newline delimiter. If limit is 0, the line length is not
|
|
158
|
+
* limited. If no newline delimiter is found before EOF, returns nil.
|
|
159
|
+
*
|
|
160
|
+
* @param limit [integer] maximum line length (0 means no limit)
|
|
161
|
+
* @return [String, nil] read data or nil
|
|
162
|
+
*/
|
|
132
163
|
VALUE Stream_get_line(VALUE self, VALUE limit) {
|
|
133
164
|
struct um_stream *stream = um_get_stream(self);
|
|
134
165
|
return stream_get_line(stream, Qnil, NUM2ULONG(limit));
|
|
135
166
|
}
|
|
136
167
|
|
|
168
|
+
/* call-seq:
|
|
169
|
+
* stream.get_string(len) -> str
|
|
170
|
+
*
|
|
171
|
+
* Reads len bytes from the stream. If len is 0, reads all available bytes. If
|
|
172
|
+
* len is negative, reads up to -len available bytes. If len is positive and eof
|
|
173
|
+
* is encountered before len bytes are read, returns nil.
|
|
174
|
+
*
|
|
175
|
+
* @param len [integer] number of bytes to read
|
|
176
|
+
* @return [String, nil] read data or nil
|
|
177
|
+
*/
|
|
137
178
|
VALUE Stream_get_string(VALUE self, VALUE len) {
|
|
138
179
|
struct um_stream *stream = um_get_stream(self);
|
|
139
180
|
return stream_get_string(stream, Qnil, NUM2LONG(len), 0, false);
|
|
140
181
|
}
|
|
141
182
|
|
|
183
|
+
/* call-seq:
|
|
184
|
+
* stream.skip(len) -> len
|
|
185
|
+
*
|
|
186
|
+
* Skips len bytes in the stream.
|
|
187
|
+
*
|
|
188
|
+
* @param len [integer] number of bytes to skip
|
|
189
|
+
* @return [Integer] len
|
|
190
|
+
*/
|
|
191
|
+
VALUE Stream_skip(VALUE self, VALUE len) {
|
|
192
|
+
struct um_stream *stream = um_get_stream(self);
|
|
193
|
+
stream_skip(stream, NUM2LONG(len), true);
|
|
194
|
+
return len;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* call-seq:
|
|
198
|
+
* stream.resp_decode -> obj
|
|
199
|
+
*
|
|
200
|
+
* Decodes an object from a RESP (Redis protocol) message.
|
|
201
|
+
*
|
|
202
|
+
* @return [any] decoded object
|
|
203
|
+
*/
|
|
142
204
|
VALUE Stream_resp_decode(VALUE self) {
|
|
143
205
|
struct um_stream *stream = um_get_stream(self);
|
|
144
206
|
VALUE out_buffer = rb_utf8_str_new_literal("");
|
|
@@ -147,6 +209,15 @@ VALUE Stream_resp_decode(VALUE self) {
|
|
|
147
209
|
return obj;
|
|
148
210
|
}
|
|
149
211
|
|
|
212
|
+
/* call-seq:
|
|
213
|
+
* stream.resp_encode(obj) -> string
|
|
214
|
+
*
|
|
215
|
+
* Encodes an object into a RESP (Redis protocol) message.
|
|
216
|
+
*
|
|
217
|
+
* @param str [String] string buffer
|
|
218
|
+
* @param obj [any] object to be encoded
|
|
219
|
+
* @return [String] str
|
|
220
|
+
*/
|
|
150
221
|
VALUE Stream_resp_encode(VALUE self, VALUE str, VALUE obj) {
|
|
151
222
|
struct um_write_buffer buf;
|
|
152
223
|
write_buffer_init(&buf, str);
|
|
@@ -156,11 +227,49 @@ VALUE Stream_resp_encode(VALUE self, VALUE str, VALUE obj) {
|
|
|
156
227
|
return str;
|
|
157
228
|
}
|
|
158
229
|
|
|
230
|
+
/* call-seq:
|
|
231
|
+
* stream.eof? -> bool
|
|
232
|
+
*
|
|
233
|
+
* Returns true if stream has reached EOF.
|
|
234
|
+
*
|
|
235
|
+
* @return [bool] EOF reached
|
|
236
|
+
*/
|
|
159
237
|
VALUE Stream_eof_p(VALUE self) {
|
|
160
238
|
struct um_stream *stream = um_get_stream(self);
|
|
161
239
|
return stream->eof ? Qtrue : Qfalse;
|
|
162
240
|
}
|
|
163
241
|
|
|
242
|
+
/* call-seq:
|
|
243
|
+
* stream.consumed -> int
|
|
244
|
+
*
|
|
245
|
+
* Returns the total number of bytes consumed from the stream.
|
|
246
|
+
*
|
|
247
|
+
* @return [Integer] total bytes consumed
|
|
248
|
+
*/
|
|
249
|
+
VALUE Stream_consumed(VALUE self) {
|
|
250
|
+
struct um_stream *stream = um_get_stream(self);
|
|
251
|
+
return LONG2NUM(stream->consumed_bytes);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* call-seq:
|
|
255
|
+
* stream.pending -> int
|
|
256
|
+
*
|
|
257
|
+
* Returns the number of bytes available for reading.
|
|
258
|
+
*
|
|
259
|
+
* @return [Integer] bytes available
|
|
260
|
+
*/
|
|
261
|
+
VALUE Stream_pending(VALUE self) {
|
|
262
|
+
struct um_stream *stream = um_get_stream(self);
|
|
263
|
+
return LONG2NUM(stream->pending_bytes);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* call-seq:
|
|
267
|
+
* stream.clear -> stream
|
|
268
|
+
*
|
|
269
|
+
* Clears all available bytes and stops any ongoing read operation.
|
|
270
|
+
*
|
|
271
|
+
* @return [UM::Stream] self
|
|
272
|
+
*/
|
|
164
273
|
VALUE Stream_clear(VALUE self) {
|
|
165
274
|
struct um_stream *stream = um_get_stream(self);
|
|
166
275
|
stream_clear(stream);
|
|
@@ -176,11 +285,14 @@ void Init_Stream(void) {
|
|
|
176
285
|
|
|
177
286
|
rb_define_method(cStream, "get_line", Stream_get_line, 1);
|
|
178
287
|
rb_define_method(cStream, "get_string", Stream_get_string, 1);
|
|
288
|
+
rb_define_method(cStream, "skip", Stream_skip, 1);
|
|
179
289
|
|
|
180
290
|
rb_define_method(cStream, "resp_decode", Stream_resp_decode, 0);
|
|
181
291
|
rb_define_singleton_method(cStream, "resp_encode", Stream_resp_encode, 2);
|
|
182
292
|
|
|
183
293
|
rb_define_method(cStream, "eof?", Stream_eof_p, 0);
|
|
294
|
+
rb_define_method(cStream, "consumed", Stream_consumed, 0);
|
|
295
|
+
rb_define_method(cStream, "pending", Stream_pending, 0);
|
|
184
296
|
rb_define_method(cStream, "clear", Stream_clear, 0);
|
|
185
297
|
|
|
186
298
|
eStreamRESPError = rb_define_class_under(cStream, "RESPError", rb_eStandardError);
|
data/grant-2025/tasks.md
CHANGED
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
- [v] Data processing through a rewritten stream implementation.
|
|
36
36
|
|
|
37
37
|
- [ ] More benchmarks
|
|
38
|
-
- Different reading methods in concurrent setting
|
|
39
|
-
- SSL vs SSL-custom-BIO vs SSL-stream
|
|
38
|
+
- [ ] Different reading methods in concurrent setting
|
|
39
|
+
- [ ] SSL vs SSL-custom-BIO vs SSL-stream in concurrent setting
|
|
40
|
+
- [ ] stream resp client vs redis gem in concurrent setting
|
|
40
41
|
|
|
41
42
|
- [v] Sidecar mode
|
|
42
43
|
- [v] Convert `UM#initialize` to take kwargs
|
data/lib/uringmachine/version.rb
CHANGED
data/lib/uringmachine.rb
CHANGED
|
@@ -194,6 +194,15 @@ class UringMachine
|
|
|
194
194
|
close_async(fd)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
|
+
def stream(target, mode = nil)
|
|
198
|
+
stream = UM::Stream.new(self, target, mode)
|
|
199
|
+
return stream if !block_given?
|
|
200
|
+
|
|
201
|
+
res = yield(stream)
|
|
202
|
+
stream.clear
|
|
203
|
+
res
|
|
204
|
+
end
|
|
205
|
+
|
|
197
206
|
private
|
|
198
207
|
|
|
199
208
|
# @param block [Proc]
|
data/test/helper.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'securerandom'
|
|
|
6
6
|
require 'socket'
|
|
7
7
|
require 'net/http'
|
|
8
8
|
require 'json'
|
|
9
|
+
require 'timeout'
|
|
9
10
|
|
|
10
11
|
class MethodCallAuditor
|
|
11
12
|
attr_reader :calls
|
|
@@ -722,12 +723,11 @@ class FiberSchedulerTest < UMBaseTest
|
|
|
722
723
|
sleep 1
|
|
723
724
|
end
|
|
724
725
|
res = true
|
|
725
|
-
rescue =>
|
|
726
|
-
res = e
|
|
726
|
+
rescue => res
|
|
727
727
|
end
|
|
728
728
|
@scheduler.join
|
|
729
|
-
assert_equal 3, machine.metrics[:total_ops]
|
|
730
729
|
assert_kind_of Timeout::Error, res
|
|
730
|
+
assert_equal 3, machine.metrics[:total_ops]
|
|
731
731
|
assert_equal({
|
|
732
732
|
fiber: 1,
|
|
733
733
|
timeout_after: 1,
|
data/test/test_stream.rb
CHANGED
|
@@ -46,7 +46,10 @@ class StreamTest < StreamBaseTest
|
|
|
46
46
|
assert stream.eof?
|
|
47
47
|
|
|
48
48
|
stream.clear
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
# initial buffer size: 6BKV, initial buffers commited: 16 (256KB)
|
|
51
|
+
# (plus an additional buffer commited after first usage)
|
|
52
|
+
assert_equal [16, 0, 256, 16384 * 16, 16384 * 16 - 6], buffer_metrics
|
|
50
53
|
assert_equal 0, machine.metrics[:ops_pending]
|
|
51
54
|
end
|
|
52
55
|
|
|
@@ -68,7 +71,7 @@ class StreamTest < StreamBaseTest
|
|
|
68
71
|
assert_equal 0, machine.metrics[:ops_pending]
|
|
69
72
|
assert_equal 256, machine.metrics[:segments_free]
|
|
70
73
|
|
|
71
|
-
assert_equal [
|
|
74
|
+
assert_equal [16, 0, 256, 16384 * 16, 16384 * 16 - 6], buffer_metrics
|
|
72
75
|
ensure
|
|
73
76
|
machine.close(rfd) rescue nil
|
|
74
77
|
machine.close(wfd) rescue nil
|
|
@@ -118,11 +121,11 @@ class StreamTest < StreamBaseTest
|
|
|
118
121
|
buf = stream.get_string(msg.bytesize)
|
|
119
122
|
assert_equal msg, buf
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
stream.clear
|
|
125
|
+
# numbers may vary with different kernel versions
|
|
126
|
+
assert_in_range 24..32, machine.metrics[:buffers_allocated]
|
|
127
|
+
assert_in_range 10..18, machine.metrics[:buffers_free]
|
|
123
128
|
assert_equal 256, machine.metrics[:segments_free]
|
|
124
|
-
assert_equal 65536 * 4, machine.metrics[:buffer_space_allocated]
|
|
125
|
-
|
|
126
129
|
ensure
|
|
127
130
|
machine.terminate(f)
|
|
128
131
|
machine.join(f)
|
|
@@ -136,7 +139,7 @@ class StreamTest < StreamBaseTest
|
|
|
136
139
|
|
|
137
140
|
assert_equal 'foo', stream.get_line(0)
|
|
138
141
|
|
|
139
|
-
assert_equal [
|
|
142
|
+
assert_equal [16, 0, 255, 16384 * 16, 16384 * 16 - 12], buffer_metrics
|
|
140
143
|
assert_equal 'bar', stream.get_line(0)
|
|
141
144
|
assert_nil stream.get_line(0)
|
|
142
145
|
assert_equal "baz", stream.get_string(-6)
|
|
@@ -152,11 +155,11 @@ class StreamTest < StreamBaseTest
|
|
|
152
155
|
machine.close(@wfd)
|
|
153
156
|
|
|
154
157
|
# three segments received
|
|
155
|
-
assert_equal [
|
|
158
|
+
assert_equal [16, 0, 253, 16384 * 16, 16384 * 16 - 13], buffer_metrics
|
|
156
159
|
assert_equal 'bar', stream.get_line(0)
|
|
157
|
-
assert_equal [
|
|
160
|
+
assert_equal [16, 0, 255, 16384 * 16, 16384 * 16 - 13], buffer_metrics
|
|
158
161
|
assert_equal 'baz', stream.get_line(0)
|
|
159
|
-
assert_equal [
|
|
162
|
+
assert_equal [16, 0, 256, 16384 * 16, 16384 * 16 - 13], buffer_metrics
|
|
160
163
|
assert_nil stream.get_line(0)
|
|
161
164
|
end
|
|
162
165
|
|
|
@@ -182,7 +185,7 @@ class StreamTest < StreamBaseTest
|
|
|
182
185
|
assert_equal 'bizz', stream.get_line(5)
|
|
183
186
|
|
|
184
187
|
assert_nil stream.get_line(8)
|
|
185
|
-
assert_equal [
|
|
188
|
+
assert_equal [16, 0, 256, 16384 * 16, 16384 * 16 - 17], buffer_metrics
|
|
186
189
|
end
|
|
187
190
|
|
|
188
191
|
def test_stream_get_string
|
|
@@ -218,6 +221,16 @@ class StreamTest < StreamBaseTest
|
|
|
218
221
|
assert_nil stream.get_string(-3)
|
|
219
222
|
end
|
|
220
223
|
|
|
224
|
+
def test_stream_skip
|
|
225
|
+
machine.write(@wfd, "foobarbaz")
|
|
226
|
+
|
|
227
|
+
stream.skip(2)
|
|
228
|
+
assert_equal 'obar', stream.get_string(4)
|
|
229
|
+
|
|
230
|
+
stream.skip(1)
|
|
231
|
+
assert_equal 'az', stream.get_string(0)
|
|
232
|
+
end
|
|
233
|
+
|
|
221
234
|
def test_stream_big_data
|
|
222
235
|
data = SecureRandom.random_bytes(300_000)
|
|
223
236
|
fiber = machine.spin {
|
|
@@ -467,6 +480,23 @@ class StreamDevRandomTest < UMBaseTest
|
|
|
467
480
|
machine.close(fd) rescue nil
|
|
468
481
|
end
|
|
469
482
|
|
|
483
|
+
def get_line_do(n, acc)
|
|
484
|
+
fd = @machine.open('/dev/random', UM::O_RDONLY)
|
|
485
|
+
stream = UM::Stream.new(@machine, fd)
|
|
486
|
+
n.times { acc << stream.get_line(0) }
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def test_stream_dev_random_get_line_concurrent
|
|
490
|
+
acc = []
|
|
491
|
+
c = 1
|
|
492
|
+
n = 100000
|
|
493
|
+
ff = c.times.map {
|
|
494
|
+
machine.spin { get_line_do(n, acc) }
|
|
495
|
+
}
|
|
496
|
+
machine.await(ff)
|
|
497
|
+
assert_equal c * n, acc.size
|
|
498
|
+
end
|
|
499
|
+
|
|
470
500
|
def test_stream_dev_random_get_string
|
|
471
501
|
fd = machine.open('/dev/random', UM::O_RDONLY)
|
|
472
502
|
stream = UM::Stream.new(machine, fd)
|
|
@@ -573,3 +603,36 @@ class StreamModeTest < UMBaseTest
|
|
|
573
603
|
sock2&.close rescue nil
|
|
574
604
|
end
|
|
575
605
|
end
|
|
606
|
+
|
|
607
|
+
class StreamByteCountsTest < StreamBaseTest
|
|
608
|
+
def test_stream_byte_counts
|
|
609
|
+
machine.write(@wfd, "foobar")
|
|
610
|
+
|
|
611
|
+
assert_equal 0, stream.consumed
|
|
612
|
+
assert_equal 0, stream.pending
|
|
613
|
+
|
|
614
|
+
buf = stream.get_string(2)
|
|
615
|
+
assert_equal 'fo', buf
|
|
616
|
+
assert_equal 2, stream.consumed
|
|
617
|
+
assert_equal 4, stream.pending
|
|
618
|
+
|
|
619
|
+
buf = stream.get_string(3)
|
|
620
|
+
assert_equal 'oba', buf
|
|
621
|
+
assert_equal 5, stream.consumed
|
|
622
|
+
assert_equal 1, stream.pending
|
|
623
|
+
|
|
624
|
+
machine.write(@wfd, "abc\ndef")
|
|
625
|
+
machine.snooze
|
|
626
|
+
assert_equal 5, stream.consumed
|
|
627
|
+
assert_equal 1, stream.pending
|
|
628
|
+
|
|
629
|
+
buf = stream.get_line(0)
|
|
630
|
+
assert_equal 'rabc', buf
|
|
631
|
+
assert_equal 10, stream.consumed
|
|
632
|
+
assert_equal 3, stream.pending
|
|
633
|
+
|
|
634
|
+
stream.clear
|
|
635
|
+
assert_equal 10, stream.consumed
|
|
636
|
+
assert_equal 0, stream.pending
|
|
637
|
+
end
|
|
638
|
+
end
|
data/test/test_um.rb
CHANGED
|
@@ -292,6 +292,20 @@ class ScheduleTest < UMBaseTest
|
|
|
292
292
|
assert_kind_of TOError, e
|
|
293
293
|
end
|
|
294
294
|
|
|
295
|
+
def test_timeout_with_exception_instance
|
|
296
|
+
res = nil
|
|
297
|
+
e = TOError.new
|
|
298
|
+
begin
|
|
299
|
+
res = machine.timeout(0.01, e) {
|
|
300
|
+
machine.sleep(1)
|
|
301
|
+
:foo
|
|
302
|
+
}
|
|
303
|
+
rescue => res
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
assert_equal e, res
|
|
307
|
+
end
|
|
308
|
+
|
|
295
309
|
def test_timeout_with_raising_block
|
|
296
310
|
e = nil
|
|
297
311
|
begin
|
|
@@ -3491,3 +3505,55 @@ class SetChildSubreaperTest < Minitest::Test
|
|
|
3491
3505
|
assert_equal grand_child_pid, res
|
|
3492
3506
|
end
|
|
3493
3507
|
end
|
|
3508
|
+
|
|
3509
|
+
class StreamMethodTest < UMBaseTest
|
|
3510
|
+
def setup
|
|
3511
|
+
super
|
|
3512
|
+
@rfd, @wfd = UM.pipe
|
|
3513
|
+
end
|
|
3514
|
+
|
|
3515
|
+
def teardown
|
|
3516
|
+
@stream = nil
|
|
3517
|
+
machine.close(@rfd) rescue nil
|
|
3518
|
+
machine.close(@wfd) rescue nil
|
|
3519
|
+
super
|
|
3520
|
+
end
|
|
3521
|
+
|
|
3522
|
+
def test_stream_method
|
|
3523
|
+
machine.write(@wfd, "foobar")
|
|
3524
|
+
machine.close(@wfd)
|
|
3525
|
+
|
|
3526
|
+
stream = machine.stream(@rfd)
|
|
3527
|
+
assert_kind_of UM::Stream, stream
|
|
3528
|
+
|
|
3529
|
+
buf = stream.get_string(3)
|
|
3530
|
+
assert_equal 'foo', buf
|
|
3531
|
+
|
|
3532
|
+
buf = stream.get_string(-6)
|
|
3533
|
+
assert_equal 'bar', buf
|
|
3534
|
+
assert stream.eof?
|
|
3535
|
+
|
|
3536
|
+
stream.clear
|
|
3537
|
+
end
|
|
3538
|
+
|
|
3539
|
+
def test_stream_method_with_block
|
|
3540
|
+
machine.write(@wfd, "foobar")
|
|
3541
|
+
machine.close(@wfd)
|
|
3542
|
+
|
|
3543
|
+
bufs = []
|
|
3544
|
+
stream_obj = nil
|
|
3545
|
+
res = machine.stream(@rfd) do |s|
|
|
3546
|
+
stream_obj = s
|
|
3547
|
+
|
|
3548
|
+
bufs << s.get_string(3)
|
|
3549
|
+
bufs << s.get_string(-6)
|
|
3550
|
+
|
|
3551
|
+
:foo
|
|
3552
|
+
end
|
|
3553
|
+
|
|
3554
|
+
assert_kind_of UM::Stream, stream_obj
|
|
3555
|
+
assert stream_obj.eof?
|
|
3556
|
+
assert_equal ['foo', 'bar'], bufs
|
|
3557
|
+
assert_equal :foo, res
|
|
3558
|
+
end
|
|
3559
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: uringmachine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.29.
|
|
4
|
+
version: 0.29.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
@@ -67,6 +67,7 @@ files:
|
|
|
67
67
|
- benchmark/common.rb
|
|
68
68
|
- benchmark/dns_client.rb
|
|
69
69
|
- benchmark/gets.rb
|
|
70
|
+
- benchmark/gets_concurrent.rb
|
|
70
71
|
- benchmark/http_parse.rb
|
|
71
72
|
- benchmark/http_server_accept_queue.rb
|
|
72
73
|
- benchmark/http_server_multi_accept.rb
|