uringmachine 0.29.1 → 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 +6 -0
- data/README.md +106 -6
- data/TODO.md +0 -1
- data/docs/design/buffer_pool.md +1 -1
- data/ext/um/um.h +2 -1
- data/ext/um/um_stream.c +13 -8
- data/ext/um/um_stream_class.c +112 -0
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +9 -0
- data/test/test_stream.rb +43 -0
- data/test/test_um.rb +52 -0
- metadata +1 -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
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
data/docs/design/buffer_pool.md
CHANGED
data/ext/um/um.h
CHANGED
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) {
|
|
@@ -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,12 +156,12 @@ 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
160
|
// int same_threshold = stream->op->bp_commit_level == stream->machine->bp_commit_level;
|
|
161
161
|
|
|
162
162
|
// fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
|
|
163
163
|
// stream,
|
|
164
|
-
// total_bytes, stream->
|
|
164
|
+
// total_bytes, stream->pending_bytes, stream->machine->bp_commit_level,
|
|
165
165
|
// stream->machine->bp_buffer_count,
|
|
166
166
|
// same_threshold, should_restart
|
|
167
167
|
// );
|
|
@@ -173,7 +173,7 @@ int stream_get_more_segments_bp(struct um_stream *stream) {
|
|
|
173
173
|
if (should_restart) {
|
|
174
174
|
if (stream->op->bp_commit_level == stream->machine->bp_commit_level)
|
|
175
175
|
bp_handle_enobufs(stream->machine);
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
um_op_release(stream->machine, stream->op);
|
|
178
178
|
stream->op = NULL;
|
|
179
179
|
// stream_multishot_op_start(stream);
|
|
@@ -235,12 +235,16 @@ inline void stream_shift_head(struct um_stream *stream) {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
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
|
+
|
|
238
241
|
while (inc) {
|
|
239
242
|
size_t segment_len = stream->head->len - stream->pos;
|
|
240
243
|
size_t inc_len = (segment_len <= inc) ? segment_len : inc;
|
|
241
244
|
inc -= inc_len;
|
|
242
245
|
stream->pos += inc_len;
|
|
243
|
-
stream->
|
|
246
|
+
stream->consumed_bytes += inc_len;
|
|
247
|
+
stream->pending_bytes -= inc_len;
|
|
244
248
|
if (stream->pos == stream->head->len) {
|
|
245
249
|
stream_shift_head(stream);
|
|
246
250
|
if (inc && safe_inc && !stream->head) {
|
|
@@ -259,7 +263,8 @@ inline void stream_copy(struct um_stream *stream, char *dest, size_t len) {
|
|
|
259
263
|
|
|
260
264
|
len -= cpy_len;
|
|
261
265
|
stream->pos += cpy_len;
|
|
262
|
-
stream->
|
|
266
|
+
stream->consumed_bytes += cpy_len;
|
|
267
|
+
stream->pending_bytes -= cpy_len;
|
|
263
268
|
dest += cpy_len;
|
|
264
269
|
if (stream->pos == stream->head->len) stream_shift_head(stream);
|
|
265
270
|
}
|
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/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/test_stream.rb
CHANGED
|
@@ -221,6 +221,16 @@ class StreamTest < StreamBaseTest
|
|
|
221
221
|
assert_nil stream.get_string(-3)
|
|
222
222
|
end
|
|
223
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
|
+
|
|
224
234
|
def test_stream_big_data
|
|
225
235
|
data = SecureRandom.random_bytes(300_000)
|
|
226
236
|
fiber = machine.spin {
|
|
@@ -593,3 +603,36 @@ class StreamModeTest < UMBaseTest
|
|
|
593
603
|
sock2&.close rescue nil
|
|
594
604
|
end
|
|
595
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
|
@@ -3505,3 +3505,55 @@ class SetChildSubreaperTest < Minitest::Test
|
|
|
3505
3505
|
assert_equal grand_child_pid, res
|
|
3506
3506
|
end
|
|
3507
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
|