uringmachine 1.0.2 → 1.0.5
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 +13 -0
- data/TODO.md +76 -53
- data/examples/irb_fiber.rb +14 -0
- data/ext/um/um.c +1 -1
- data/ext/um/um.h +3 -0
- data/ext/um/um_io.c +133 -43
- data/ext/um/um_io_class.c +51 -6
- data/ext/um/um_op.c +1 -2
- data/lib/uringmachine/version.rb +1 -1
- data/test/test_io.rb +144 -0
- data/test/test_um.rb +44 -4
- 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: 8c4ce5de22052f74a5386dafe18ff4f021db995b6a6b120516a98e0f81783222
|
|
4
|
+
data.tar.gz: 64b51a3e8c2e1d5392c6cd8c4c995c09b506de554a537bad14a3074702fb0926
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 458534cc0ceec394b4dcf32829f62708ac1c68a8e7adf71f4bb48fe00195594805fe22ecc990edc38790b44e9609eeee00ca0c537bc316136e00bb3e87b69577
|
|
7
|
+
data.tar.gz: d955545beb0ba009b85ae3fa11619bf3a8869ad97474dd041e57d2c903555418e40e22eae2707a4e936563196162cec197ba55f516fb475cdb6f71da62fb9e87
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# 1.0.5 2026-10-01
|
|
2
|
+
|
|
3
|
+
- Add support for regular files in UM::IO with :file mode
|
|
4
|
+
|
|
5
|
+
# 1.0.4 2026-08-24
|
|
6
|
+
|
|
7
|
+
- Add `IO#read_uint_be` method
|
|
8
|
+
|
|
9
|
+
# 1.0.3 2026-08-24
|
|
10
|
+
|
|
11
|
+
- Add `IO#read_int_be` method
|
|
12
|
+
- Don't schedule fiber for async multishot ops (fixes potential segfault when using UM::IO)
|
|
13
|
+
|
|
1
14
|
# 1.0.2 2026-05-25
|
|
2
15
|
|
|
3
16
|
- Fix DNS resolver
|
data/TODO.md
CHANGED
|
@@ -1,14 +1,84 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Character scanning in UM::IO
|
|
2
|
+
|
|
3
|
+
- Refactor character scanning
|
|
4
|
+
- functions/macros for iterating over segments
|
|
5
|
+
|
|
6
|
+
- SIMD?
|
|
7
|
+
- prep work for SIMD character scanning
|
|
8
|
+
- we'll probably need a bit of helper code for dealing with SIMD
|
|
9
|
+
- a good overview here: https://mitchellh.com/writing/everyone-should-know-simd
|
|
10
|
+
- SIMD prep work should be done once before iterating over segments
|
|
11
|
+
|
|
12
|
+
```c
|
|
13
|
+
// bitmaps for character types can be generated with a bit of Ruby:
|
|
14
|
+
//
|
|
15
|
+
// def t(r); (0..255).map { [it].pack('c') =~ r ? 1 : 0 }; end
|
|
16
|
+
// def tn(r); (0..255).map { [it].pack('c') =~ r ? 0 : 1 }; end
|
|
17
|
+
// def u64(bits); bits.reverse.join.to_i(2); end
|
|
18
|
+
// def p(a); a.each_slice(64).map { u64(it) }; end
|
|
2
19
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
20
|
+
// usage:
|
|
21
|
+
//
|
|
22
|
+
// p(t(/[a-zA-Z0-9]/)).map { format('%016X', it) }
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// /[a-zA-Z0-9]/
|
|
26
|
+
uint64_t alpha_numeric[] = [
|
|
27
|
+
0x000000000000FFC0,
|
|
28
|
+
0x7FFFFFE07FFFFFE0,
|
|
29
|
+
0x0000000000000000,
|
|
30
|
+
0x0000000000000000
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// HTTP method: /[a-zA-Z]/ (3-12 characters)
|
|
34
|
+
// header-key: /[a-zA-Z\-]/ ()
|
|
35
|
+
// path: /^($/
|
|
36
|
+
|
|
37
|
+
// check if character is in bitmap
|
|
38
|
+
inline int test_char(char c, uint64 *bitmap) {
|
|
39
|
+
return bitmap[c / 64] & (1UL << (c % 64));
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## IO methods
|
|
44
|
+
|
|
45
|
+
- IO methods:
|
|
46
|
+
```ruby
|
|
47
|
+
io.read_int_be(length)
|
|
48
|
+
io.read_uint_be(length)
|
|
49
|
+
io.read_int_le(length)
|
|
50
|
+
io.read_uint_le(length)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- It might be interesting to have a DSL where we can define a frame/header structure:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
http2_transform = UM::IO.transform do
|
|
57
|
+
length uint(24)
|
|
58
|
+
type uint(8)
|
|
59
|
+
flags uint(8)
|
|
60
|
+
stream_id uint(32)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
frame = io.read(http2_transform)
|
|
64
|
+
raise if frame[:length] > MAX_LENGTH
|
|
65
|
+
raise if stream_id & 0x80000000
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- Add `IO#http1_xxx` methods
|
|
69
|
+
- `#http1_read_request_headers()`
|
|
70
|
+
- `#http1_read_body(content_length)` (-1 means chunked TE)
|
|
6
71
|
|
|
7
72
|
- Add tests for support for Set in `machine#await`
|
|
8
73
|
- Add tests for support for Set, Array in `machine#join`
|
|
9
|
-
- Add `UM#read_file` for reading entire file
|
|
74
|
+
- Add `UM#read_file` for reading entire file
|
|
10
75
|
- Add `UM#write_file` for writing entire file
|
|
11
76
|
|
|
77
|
+
## SSL custom BIO:
|
|
78
|
+
|
|
79
|
+
- release GVL before calling `SSL_read`, `SSL_write`
|
|
80
|
+
- reacquire GVL (`rb_thread_call_with_gvl`) in `um_bio_read`, `um_bio_write`
|
|
81
|
+
|
|
12
82
|
## Balancing I/O with the runqueue
|
|
13
83
|
|
|
14
84
|
- in some cases where there are many entries in the runqueue, this can
|
|
@@ -71,6 +141,7 @@
|
|
|
71
141
|
- select on fibers:
|
|
72
142
|
- select fibers that are done
|
|
73
143
|
- select first done fiber
|
|
144
|
+
- we might want to rename `#select` to `#fdselect`
|
|
74
145
|
|
|
75
146
|
## ops still not implemented
|
|
76
147
|
|
|
@@ -84,21 +155,6 @@
|
|
|
84
155
|
- madvise
|
|
85
156
|
- getxattr / setxattr
|
|
86
157
|
|
|
87
|
-
## actors
|
|
88
|
-
|
|
89
|
-
When doing a `call`, we need to provide a mailbox for the response. can this be
|
|
90
|
-
automatic?
|
|
91
|
-
|
|
92
|
-
## Syntax / pattern for launching/supervising multiple operations
|
|
93
|
-
|
|
94
|
-
Select (see above):
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
# select
|
|
98
|
-
machine.join_select(*fibers) #=> [result, fiber]
|
|
99
|
-
machine.shift_select(*queues) #=> [result, queue]
|
|
100
|
-
```
|
|
101
|
-
|
|
102
158
|
## Other abstractions
|
|
103
159
|
|
|
104
160
|
- Happy eyeballs connect
|
|
@@ -108,36 +164,3 @@ machine.shift_select(*queues) #=> [result, queue]
|
|
|
108
164
|
# ['1.1.1.1:80', '2.2.2.2:80']
|
|
109
165
|
tcp_connect_he(*addrs)
|
|
110
166
|
```
|
|
111
|
-
|
|
112
|
-
## Character scanning in UM::IO
|
|
113
|
-
|
|
114
|
-
```c
|
|
115
|
-
// bitmaps for character types can be generated with a bit of Ruby:
|
|
116
|
-
//
|
|
117
|
-
// def t(r); (0..255).map { [it].pack('c') =~ r ? 1 : 0 }; end
|
|
118
|
-
// def tn(r); (0..255).map { [it].pack('c') =~ r ? 0 : 1 }; end
|
|
119
|
-
// def u64(bits); bits.reverse.join.to_i(2); end
|
|
120
|
-
// def p(a); a.each_slice(64).map { u64(it) }; end
|
|
121
|
-
|
|
122
|
-
// usage:
|
|
123
|
-
//
|
|
124
|
-
// p(t(/[a-zA-Z0-9]/)).map { format('%016X', it) }
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// /[a-zA-Z0-9]/
|
|
128
|
-
uint64_t alpha_numeric[] = [
|
|
129
|
-
0x000000000000FFC0,
|
|
130
|
-
0x7FFFFFE07FFFFFE0,
|
|
131
|
-
0x0000000000000000,
|
|
132
|
-
0x0000000000000000
|
|
133
|
-
];
|
|
134
|
-
|
|
135
|
-
// HTTP method: /[a-zA-Z]/ (3-12 characters)
|
|
136
|
-
// header-key: /[a-zA-Z\-]/ ()
|
|
137
|
-
// path: /^($/
|
|
138
|
-
|
|
139
|
-
// check if character is in bitmap
|
|
140
|
-
inline int test_char(char c, uint64 *bitmap) {
|
|
141
|
-
return bitmap[c / 64] & (1UL << (c % 64));
|
|
142
|
-
}
|
|
143
|
-
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
require 'uringmachine'
|
|
6
|
+
require 'uringmachine/fiber_scheduler'
|
|
7
|
+
require 'irb'
|
|
8
|
+
|
|
9
|
+
@machine = UringMachine.new
|
|
10
|
+
@scheduler = UM::FiberScheduler.new(@machine)
|
|
11
|
+
Fiber.set_scheduler @scheduler
|
|
12
|
+
|
|
13
|
+
f = @machine.spin { IRB.start }
|
|
14
|
+
@machine.join(f)
|
data/ext/um/um.c
CHANGED
|
@@ -192,7 +192,7 @@ static inline void um_process_cqe(struct um *machine, struct io_uring_cqe *cqe)
|
|
|
192
192
|
um_op_multishot_results_push(machine, op, cqe->res, cqe->flags);
|
|
193
193
|
|
|
194
194
|
op->flags |= done ? (OP_F_CQE_SEEN | OP_F_CQE_DONE) : OP_F_CQE_SEEN;
|
|
195
|
-
if (!
|
|
195
|
+
if (!(op->flags & (OP_F_SCHEDULED | OP_F_ASYNC))) um_schedule_op(machine, op);
|
|
196
196
|
}
|
|
197
197
|
else {
|
|
198
198
|
// single shot
|
data/ext/um/um.h
CHANGED
|
@@ -83,6 +83,7 @@ enum um_io_mode {
|
|
|
83
83
|
IO_FD,
|
|
84
84
|
IO_SOCKET,
|
|
85
85
|
IO_SSL,
|
|
86
|
+
IO_FILE,
|
|
86
87
|
IO_STRING,
|
|
87
88
|
IO_IO_BUFFER
|
|
88
89
|
};
|
|
@@ -426,6 +427,8 @@ VALUE io_read(struct um_io *io, VALUE out_buffer, ssize_t len, size_t inc, int s
|
|
|
426
427
|
VALUE io_read_to_delim(struct um_io *io, VALUE out_buffer, VALUE delim, ssize_t maxlen);
|
|
427
428
|
void io_skip(struct um_io *io, size_t inc, int safe_inc);
|
|
428
429
|
void io_read_each(struct um_io *io);
|
|
430
|
+
VALUE io_read_int_be(struct um_io *io, size_t len);
|
|
431
|
+
VALUE io_read_uint_be(struct um_io *io, size_t len);
|
|
429
432
|
size_t io_write_raw(struct um_io *io, const char *buffer, size_t len);
|
|
430
433
|
VALUE io_writev(struct um_io *io, int argc, VALUE *argv);
|
|
431
434
|
VALUE resp_read(struct um_io *io, VALUE out_buffer);
|
data/ext/um/um_io.c
CHANGED
|
@@ -74,13 +74,12 @@ void um_io_cleanup(struct um_io *io) {
|
|
|
74
74
|
um_segment_checkin(io->machine, io->head);
|
|
75
75
|
io->head = next;
|
|
76
76
|
}
|
|
77
|
+
io->tail = NULL;
|
|
77
78
|
io->pending_bytes = 0;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
// returns true if case of ENOBUFS error, sets more to true if more data forthcoming
|
|
81
|
-
inline int io_process_segments(
|
|
82
|
-
struct um_io *io, size_t *total_bytes, int *more) {
|
|
83
|
-
|
|
82
|
+
inline int io_process_segments(struct um_io *io, size_t *total_bytes, int *more) {
|
|
84
83
|
*more = 0;
|
|
85
84
|
struct um_op_result *result = &io->op->result;
|
|
86
85
|
io->op->flags &= ~OP_F_CQE_SEEN;
|
|
@@ -125,6 +124,7 @@ void io_clear(struct um_io *io) {
|
|
|
125
124
|
um_segment_checkin(io->machine, io->head);
|
|
126
125
|
io->head = next;
|
|
127
126
|
}
|
|
127
|
+
io->tail = NULL;
|
|
128
128
|
io->pending_bytes = 0;
|
|
129
129
|
|
|
130
130
|
if (io->working_buffer) {
|
|
@@ -133,7 +133,31 @@ void io_clear(struct um_io *io) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
inline void
|
|
136
|
+
inline void io_handle_enobufs(struct um_io *io) {
|
|
137
|
+
// we want to restart only if in case we don't already have lots of pending
|
|
138
|
+
// bytes in the stream. This heuristic might need a closer look to determine
|
|
139
|
+
// if this is the correct behavior.
|
|
140
|
+
int should_restart = io->pending_bytes < (io->machine->bp_buffer_size * 4);
|
|
141
|
+
|
|
142
|
+
// fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
|
|
143
|
+
// io,
|
|
144
|
+
// total_bytes, io->pending_bytes, io->machine->bp_commit_level,
|
|
145
|
+
// io->machine->bp_buffer_count,
|
|
146
|
+
// same_threshold, should_restart
|
|
147
|
+
// );
|
|
148
|
+
|
|
149
|
+
if (should_restart) {
|
|
150
|
+
// If multiple IO ops are happening at the same time, they'll all get
|
|
151
|
+
// ENOBUFS! We track the buffer pool commit level in the op in order to
|
|
152
|
+
// prevent running bp_handle_enobufs() more than once.
|
|
153
|
+
if (io->op->bp_commit_level == io->machine->bp_commit_level)
|
|
154
|
+
bp_handle_enobufs(io->machine);
|
|
155
|
+
}
|
|
156
|
+
um_op_release(io->machine, io->op);
|
|
157
|
+
io->op = NULL;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static inline void io_await_segments(struct um_io *io) {
|
|
137
161
|
if (unlikely(!io->op)) io_multishot_op_start(io);
|
|
138
162
|
|
|
139
163
|
if (!OP_CQE_SEEN_P(io->op)) {
|
|
@@ -156,40 +180,12 @@ int io_get_more_segments_bp(struct um_io *io) {
|
|
|
156
180
|
io_await_segments(io);
|
|
157
181
|
enobufs = io_process_segments(io, &total_bytes, &more);
|
|
158
182
|
um_op_multishot_results_clear(io->machine, io->op);
|
|
159
|
-
if (unlikely(enobufs)) {
|
|
160
|
-
int should_restart = io->pending_bytes < (io->machine->bp_buffer_size * 4);
|
|
161
|
-
// int same_threshold = io->op->bp_commit_level == io->machine->bp_commit_level;
|
|
162
|
-
|
|
163
|
-
// fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
|
|
164
|
-
// io,
|
|
165
|
-
// total_bytes, io->pending_bytes, io->machine->bp_commit_level,
|
|
166
|
-
// io->machine->bp_buffer_count,
|
|
167
|
-
// same_threshold, should_restart
|
|
168
|
-
// );
|
|
169
|
-
|
|
170
|
-
// If multiple IO ops are happening at the same time, they'll all
|
|
171
|
-
// get ENOBUFS! We track the commit threshold in the op in order to
|
|
172
|
-
// prevent running bp_handle_enobufs() more than once.
|
|
173
|
-
|
|
174
|
-
if (should_restart) {
|
|
175
|
-
if (io->op->bp_commit_level == io->machine->bp_commit_level)
|
|
176
|
-
bp_handle_enobufs(io->machine);
|
|
177
|
-
|
|
178
|
-
um_op_release(io->machine, io->op);
|
|
179
|
-
io->op = NULL;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
um_op_release(io->machine, io->op);
|
|
183
|
-
io->op = NULL;
|
|
184
|
-
}
|
|
185
183
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
else
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (total_bytes || io->eof) return total_bytes;
|
|
192
|
-
}
|
|
184
|
+
if (unlikely(enobufs))
|
|
185
|
+
io_handle_enobufs(io);
|
|
186
|
+
else if (more)
|
|
187
|
+
io->op->flags &= ~OP_F_CQE_SEEN;
|
|
188
|
+
if (total_bytes || io->eof) return total_bytes;
|
|
193
189
|
}
|
|
194
190
|
}
|
|
195
191
|
|
|
@@ -212,11 +208,54 @@ int io_get_more_segments_ssl(struct um_io *io) {
|
|
|
212
208
|
return 1;
|
|
213
209
|
}
|
|
214
210
|
|
|
211
|
+
int io_get_more_segments_file(struct um_io *io) {
|
|
212
|
+
if (!io->working_buffer)
|
|
213
|
+
io->working_buffer = bp_buffer_checkout(io->machine);
|
|
214
|
+
|
|
215
|
+
char *ptr = io->working_buffer->buf + io->working_buffer->pos;
|
|
216
|
+
size_t maxlen = io->working_buffer->len - io->working_buffer->pos;
|
|
217
|
+
|
|
218
|
+
struct um_op *op = um_op_acquire(io->machine);
|
|
219
|
+
um_prep_op(io->machine, op, OP_READ, 2, 0);
|
|
220
|
+
struct io_uring_sqe *sqe = um_get_sqe(io->machine, op);
|
|
221
|
+
io_uring_prep_read(sqe, io->fd, ptr, maxlen, -1);
|
|
222
|
+
|
|
223
|
+
VALUE ret = um_yield(io->machine);
|
|
224
|
+
int has_more = 0;
|
|
225
|
+
|
|
226
|
+
if (likely(um_verify_op_completion(io->machine, op, true))) {
|
|
227
|
+
int res = op->result.res;
|
|
228
|
+
|
|
229
|
+
if (res == 0) {
|
|
230
|
+
bp_buffer_checkin(io->machine, io->working_buffer);
|
|
231
|
+
io->working_buffer = NULL;
|
|
232
|
+
goto done;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
struct um_segment *segment = bp_buffer_consume(io->machine, io->working_buffer, res);
|
|
236
|
+
io_add_segment(io, segment);
|
|
237
|
+
|
|
238
|
+
if ((size_t)res == maxlen) {
|
|
239
|
+
bp_buffer_checkin(io->machine, io->working_buffer);
|
|
240
|
+
io->working_buffer = NULL;
|
|
241
|
+
}
|
|
242
|
+
else has_more = 1;
|
|
243
|
+
}
|
|
244
|
+
um_op_release(io->machine, op);
|
|
245
|
+
|
|
246
|
+
done:
|
|
247
|
+
RAISE_IF_EXCEPTION(ret);
|
|
248
|
+
RB_GC_GUARD(ret);
|
|
249
|
+
return has_more;
|
|
250
|
+
}
|
|
251
|
+
|
|
215
252
|
int io_get_more_segments(struct um_io *io) {
|
|
216
253
|
switch (io->mode) {
|
|
217
254
|
case IO_FD:
|
|
218
255
|
case IO_SOCKET:
|
|
219
256
|
return io_get_more_segments_bp(io);
|
|
257
|
+
case IO_FILE:
|
|
258
|
+
return io_get_more_segments_file(io);
|
|
220
259
|
case IO_SSL:
|
|
221
260
|
return io_get_more_segments_ssl(io);
|
|
222
261
|
default:
|
|
@@ -254,9 +293,7 @@ inline void io_skip(struct um_io *io, size_t inc, int safe_inc) {
|
|
|
254
293
|
io->pending_bytes -= inc_len;
|
|
255
294
|
if (io->pos == io->head->len) {
|
|
256
295
|
io_shift_head(io);
|
|
257
|
-
if (inc && safe_inc && !io->head)
|
|
258
|
-
if (!io_get_more_segments(io)) break;
|
|
259
|
-
}
|
|
296
|
+
if (inc && safe_inc && !io->head && !io_get_more_segments(io)) break;
|
|
260
297
|
}
|
|
261
298
|
}
|
|
262
299
|
}
|
|
@@ -285,6 +322,58 @@ inline void io_read_each(struct um_io *io) {
|
|
|
285
322
|
RB_GC_GUARD(buffer);
|
|
286
323
|
}
|
|
287
324
|
|
|
325
|
+
VALUE io_read_int_be(struct um_io *io, size_t len) {
|
|
326
|
+
if (unlikely(io->eof && !io->head)) return Qnil;
|
|
327
|
+
if (!io->tail && !io_get_more_segments(io)) return Qnil;
|
|
328
|
+
|
|
329
|
+
long value = 0;
|
|
330
|
+
struct um_segment *current = io->head;
|
|
331
|
+
|
|
332
|
+
while (true) {
|
|
333
|
+
size_t segment_len = current->len - io->pos;
|
|
334
|
+
int pos = 0;
|
|
335
|
+
while (segment_len > 0 && len > 0) {
|
|
336
|
+
value = value * 0x100 + *(unsigned char *)(current->ptr + io->pos + pos);
|
|
337
|
+
len--;
|
|
338
|
+
segment_len--;
|
|
339
|
+
pos++;
|
|
340
|
+
}
|
|
341
|
+
if (pos > 0) io_skip(io, pos, true);
|
|
342
|
+
|
|
343
|
+
if (len == 0) return LONG2NUM(value);
|
|
344
|
+
|
|
345
|
+
if (!io->head && !io_get_more_segments(io))
|
|
346
|
+
return Qnil;
|
|
347
|
+
current = io->head;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
VALUE io_read_uint_be(struct um_io *io, size_t len) {
|
|
352
|
+
if (unlikely(io->eof && !io->head)) return Qnil;
|
|
353
|
+
if (!io->tail && !io_get_more_segments(io)) return Qnil;
|
|
354
|
+
|
|
355
|
+
unsigned long value = 0;
|
|
356
|
+
struct um_segment *current = io->head;
|
|
357
|
+
|
|
358
|
+
while (true) {
|
|
359
|
+
size_t segment_len = current->len - io->pos;
|
|
360
|
+
int pos = 0;
|
|
361
|
+
while (segment_len > 0 && len > 0) {
|
|
362
|
+
value = value * 0x100 + *(unsigned char *)(current->ptr + io->pos + pos);
|
|
363
|
+
len--;
|
|
364
|
+
segment_len--;
|
|
365
|
+
pos++;
|
|
366
|
+
}
|
|
367
|
+
if (pos > 0) io_skip(io, pos, true);
|
|
368
|
+
|
|
369
|
+
if (len == 0) return ULONG2NUM(value);
|
|
370
|
+
|
|
371
|
+
if (!io->head && !io_get_more_segments(io))
|
|
372
|
+
return Qnil;
|
|
373
|
+
current = io->head;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
288
377
|
inline void io_copy(struct um_io *io, char *dest, size_t len) {
|
|
289
378
|
while (len) {
|
|
290
379
|
char *segment_ptr = io->head->ptr + io->pos;
|
|
@@ -316,7 +405,7 @@ VALUE io_consume_string(struct um_io *io, VALUE out_buffer, size_t len, size_t i
|
|
|
316
405
|
char *dest = RSTRING_PTR(str);
|
|
317
406
|
|
|
318
407
|
io_copy(io, dest, len);
|
|
319
|
-
io_skip(io, inc, safe_inc);
|
|
408
|
+
if (inc) io_skip(io, inc, safe_inc);
|
|
320
409
|
return str;
|
|
321
410
|
RB_GC_GUARD(str);
|
|
322
411
|
}
|
|
@@ -392,14 +481,15 @@ VALUE io_read(struct um_io *io, VALUE out_buffer, ssize_t len, size_t inc, int s
|
|
|
392
481
|
|
|
393
482
|
while (true) {
|
|
394
483
|
size_t segment_len = current->len - pos;
|
|
395
|
-
if (abs_len && segment_len > remaining_len) {
|
|
484
|
+
if (abs_len && segment_len > (size_t)remaining_len) {
|
|
396
485
|
segment_len = remaining_len;
|
|
397
486
|
}
|
|
398
487
|
total_len += segment_len;
|
|
399
488
|
if (abs_len) {
|
|
400
489
|
remaining_len -= segment_len;
|
|
401
|
-
if (!remaining_len)
|
|
490
|
+
if (!remaining_len) {
|
|
402
491
|
return io_consume_string(io, out_buffer, total_len, inc, safe_inc);
|
|
492
|
+
}
|
|
403
493
|
}
|
|
404
494
|
|
|
405
495
|
if (!current->next) {
|
data/ext/um/um_io_class.c
CHANGED
|
@@ -4,6 +4,7 @@ VALUE cIO;
|
|
|
4
4
|
VALUE eIORESPError;
|
|
5
5
|
|
|
6
6
|
VALUE SYM_fd;
|
|
7
|
+
VALUE SYM_file;
|
|
7
8
|
VALUE SYM_socket;
|
|
8
9
|
VALUE SYM_ssl;
|
|
9
10
|
|
|
@@ -87,6 +88,7 @@ static inline void io_set_target(struct um_io *conn, VALUE target, enum um_io_mo
|
|
|
87
88
|
conn->mode = mode;
|
|
88
89
|
switch (mode) {
|
|
89
90
|
case IO_FD:
|
|
91
|
+
case IO_FILE:
|
|
90
92
|
case IO_SOCKET:
|
|
91
93
|
conn->fd = NUM2INT(target);
|
|
92
94
|
return;
|
|
@@ -109,6 +111,8 @@ static inline void io_setup(struct um_io *conn, VALUE target, VALUE mode) {
|
|
|
109
111
|
}
|
|
110
112
|
else if (mode == SYM_fd)
|
|
111
113
|
io_set_target(conn, target, IO_FD);
|
|
114
|
+
else if (mode == SYM_file)
|
|
115
|
+
io_set_target(conn, target, IO_FILE);
|
|
112
116
|
else if (mode == SYM_socket)
|
|
113
117
|
io_set_target(conn, target, IO_SOCKET);
|
|
114
118
|
else if (mode == SYM_ssl)
|
|
@@ -157,10 +161,11 @@ VALUE IO_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
157
161
|
VALUE IO_mode(VALUE self) {
|
|
158
162
|
struct um_io *conn = um_get_io(self);
|
|
159
163
|
switch (conn->mode) {
|
|
160
|
-
case IO_FD:
|
|
161
|
-
case
|
|
162
|
-
case
|
|
163
|
-
|
|
164
|
+
case IO_FD: return SYM_fd;
|
|
165
|
+
case IO_FILE: return SYM_file;
|
|
166
|
+
case IO_SOCKET: return SYM_socket;
|
|
167
|
+
case IO_SSL: return SYM_ssl;
|
|
168
|
+
default: return Qnil;
|
|
164
169
|
}
|
|
165
170
|
return Qnil;
|
|
166
171
|
}
|
|
@@ -242,6 +247,42 @@ VALUE IO_read_each(VALUE self) {
|
|
|
242
247
|
return self;
|
|
243
248
|
}
|
|
244
249
|
|
|
250
|
+
#define MAX_INT_SIZE 8
|
|
251
|
+
|
|
252
|
+
/* call-seq:
|
|
253
|
+
* conn.read_int_be(len) -> int
|
|
254
|
+
*
|
|
255
|
+
* Reads a signed integer from the target in big endian notation. The len
|
|
256
|
+
* parameter indicates the integer field length in bytes, with an upper limit of
|
|
257
|
+
* 8 bytes. If EOF is encountered before enough bytes are read, returns nil.
|
|
258
|
+
*
|
|
259
|
+
* @param len [Integer] number of bytes to read
|
|
260
|
+
* @return [Integer, nil] integer value or nil if EOF
|
|
261
|
+
*/
|
|
262
|
+
VALUE IO_read_int_be(VALUE self, VALUE len) {
|
|
263
|
+
struct um_io *conn = um_get_io(self);
|
|
264
|
+
size_t len_i = NUM2UINT(len);
|
|
265
|
+
if (unlikely(len_i > MAX_INT_SIZE)) rb_raise(eUMError, "Invalid length given");
|
|
266
|
+
return io_read_int_be(conn, len_i);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* call-seq:
|
|
270
|
+
* conn.read_uint_be(len) -> int
|
|
271
|
+
*
|
|
272
|
+
* Reads an unsigned integer from the target in big endian notation. The len
|
|
273
|
+
* parameter indicates the integer field length in bytes, with an upper limit
|
|
274
|
+
* of 8 bytes. If EOF is encountered before enough bytes are read, returns nil.
|
|
275
|
+
*
|
|
276
|
+
* @param len [Integer] number of bytes to read
|
|
277
|
+
* @return [Integer, nil] integer value or nil if EOF
|
|
278
|
+
*/
|
|
279
|
+
VALUE IO_read_uint_be(VALUE self, VALUE len) {
|
|
280
|
+
struct um_io *conn = um_get_io(self);
|
|
281
|
+
size_t len_i = NUM2UINT(len);
|
|
282
|
+
if (unlikely(len_i > MAX_INT_SIZE)) rb_raise(eUMError, "Invalid length given");
|
|
283
|
+
return io_read_uint_be(conn, len_i);
|
|
284
|
+
}
|
|
285
|
+
|
|
245
286
|
/* call-seq:
|
|
246
287
|
* conn.write(*bufs) -> len
|
|
247
288
|
*
|
|
@@ -375,6 +416,9 @@ void Init_IO(void) {
|
|
|
375
416
|
rb_define_method(cIO, "skip", IO_skip, 1);
|
|
376
417
|
rb_define_method(cIO, "read_each", IO_read_each, 0);
|
|
377
418
|
|
|
419
|
+
rb_define_method(cIO, "read_int_be", IO_read_int_be, 1);
|
|
420
|
+
rb_define_method(cIO, "read_uint_be", IO_read_uint_be, 1);
|
|
421
|
+
|
|
378
422
|
rb_define_method(cIO, "write", IO_write, -1);
|
|
379
423
|
|
|
380
424
|
rb_define_method(cIO, "resp_read", IO_resp_read, 0);
|
|
@@ -388,7 +432,8 @@ void Init_IO(void) {
|
|
|
388
432
|
|
|
389
433
|
eIORESPError = rb_define_class_under(cIO, "RESPError", rb_eStandardError);
|
|
390
434
|
|
|
391
|
-
SYM_fd
|
|
392
|
-
|
|
435
|
+
SYM_fd = ID2SYM(rb_intern("fd"));
|
|
436
|
+
SYM_file = ID2SYM(rb_intern("file"));
|
|
437
|
+
SYM_socket = ID2SYM(rb_intern("socket"));
|
|
393
438
|
SYM_ssl = ID2SYM(rb_intern("ssl"));
|
|
394
439
|
}
|
data/ext/um/um_op.c
CHANGED
|
@@ -156,9 +156,8 @@ inline void um_op_multishot_results_push(struct um *machine, struct um_op *op, _
|
|
|
156
156
|
result->res = res;
|
|
157
157
|
result->flags = flags;
|
|
158
158
|
result->next = NULL;
|
|
159
|
-
if (op->flags & OP_F_BUFFER_POOL && res >= 0)
|
|
159
|
+
if (op->flags & OP_F_BUFFER_POOL && res >= 0)
|
|
160
160
|
result->segment = bp_get_op_result_segment(machine, op, res, flags);
|
|
161
|
-
}
|
|
162
161
|
|
|
163
162
|
op->multishot_result_tail = result;
|
|
164
163
|
op->multishot_result_count++;
|
data/lib/uringmachine/version.rb
CHANGED
data/test/test_io.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'helper'
|
|
|
4
4
|
require 'securerandom'
|
|
5
5
|
require 'openssl'
|
|
6
6
|
require 'localhost/authority'
|
|
7
|
+
require 'tempfile'
|
|
7
8
|
|
|
8
9
|
class IOBaseTest < UMBaseTest
|
|
9
10
|
attr_reader :conn
|
|
@@ -305,6 +306,22 @@ class IOTest < IOBaseTest
|
|
|
305
306
|
machine.terminate(f)
|
|
306
307
|
machine.join(f)
|
|
307
308
|
end
|
|
309
|
+
|
|
310
|
+
def test_io_read_segfault
|
|
311
|
+
machine.write(@wfd, "abcd")
|
|
312
|
+
conn.read(4)
|
|
313
|
+
|
|
314
|
+
machine.write(@wfd, "\x00\x01\x05\xdf")
|
|
315
|
+
conn.read(4)
|
|
316
|
+
|
|
317
|
+
f = machine.spin do
|
|
318
|
+
machine.write(@wfd, "\xde")
|
|
319
|
+
machine.snooze
|
|
320
|
+
machine.write(@wfd, "\x90\x03")
|
|
321
|
+
end
|
|
322
|
+
value = conn.read(3)
|
|
323
|
+
assert_equal "\xde\x90\x03".b, value
|
|
324
|
+
end
|
|
308
325
|
end
|
|
309
326
|
|
|
310
327
|
class IOWriteTest < UMBaseTest
|
|
@@ -699,6 +716,23 @@ class IOModeTest < UMBaseTest
|
|
|
699
716
|
sock2&.close rescue nil
|
|
700
717
|
end
|
|
701
718
|
|
|
719
|
+
def test_io_file_mode
|
|
720
|
+
fn = Tempfile.new('uringmachine_test_io_from_file').path
|
|
721
|
+
IO.write(fn, (0..9).to_a.join)
|
|
722
|
+
fd = machine.open(fn, UM::O_RDONLY)
|
|
723
|
+
io = machine.io(fd, :file)
|
|
724
|
+
buf = io.read(3)
|
|
725
|
+
assert_equal '012', buf
|
|
726
|
+
|
|
727
|
+
buf = io.read(5)
|
|
728
|
+
assert_equal '34567', buf
|
|
729
|
+
|
|
730
|
+
buf = io.read(-4)
|
|
731
|
+
assert_equal '89', buf
|
|
732
|
+
ensure
|
|
733
|
+
machine.close(fd) if fd
|
|
734
|
+
end
|
|
735
|
+
|
|
702
736
|
def test_io_socket_mode_non_socket
|
|
703
737
|
r, w = UM.pipe
|
|
704
738
|
machine.write(w, 'foobar')
|
|
@@ -806,3 +840,113 @@ class IOByteCountsTest < IOBaseTest
|
|
|
806
840
|
assert_equal 0, conn.pending
|
|
807
841
|
end
|
|
808
842
|
end
|
|
843
|
+
|
|
844
|
+
class IOIntTest < IOBaseTest
|
|
845
|
+
def test_io_read_int_be
|
|
846
|
+
machine.write(@wfd, "abcd")
|
|
847
|
+
conn.read(4)
|
|
848
|
+
|
|
849
|
+
machine.write(@wfd, "\x00\x01\x05\xdf")
|
|
850
|
+
value = conn.read_int_be(2)
|
|
851
|
+
assert_equal 0x0001, value
|
|
852
|
+
value = conn.read_int_be(2)
|
|
853
|
+
assert_equal 0x05df, value
|
|
854
|
+
|
|
855
|
+
machine.write(@wfd, "\x00\x01\x05\xdf")
|
|
856
|
+
value = conn.read_int_be(4)
|
|
857
|
+
assert_equal 0x000105df, value
|
|
858
|
+
|
|
859
|
+
machine.write(@wfd, "\xde\xfa")
|
|
860
|
+
machine.snooze
|
|
861
|
+
machine.write(@wfd, "\xdd\x03")
|
|
862
|
+
machine.snooze
|
|
863
|
+
value = conn.read_int_be(4)
|
|
864
|
+
assert_equal 0xdefadd03, value
|
|
865
|
+
|
|
866
|
+
machine.write(@wfd, "\xed\xcb\xa9")
|
|
867
|
+
value = conn.read_int_be(3)
|
|
868
|
+
assert_equal 0xedcba9, value
|
|
869
|
+
|
|
870
|
+
f = machine.spin do
|
|
871
|
+
machine.write(@wfd, "\xde")
|
|
872
|
+
machine.snooze
|
|
873
|
+
machine.write(@wfd, "\x90\x03")
|
|
874
|
+
end
|
|
875
|
+
value = conn.read_int_be(3)
|
|
876
|
+
machine.join(f)
|
|
877
|
+
assert_equal 0xde9003, value
|
|
878
|
+
|
|
879
|
+
machine.write(@wfd, "\xde\xfa")
|
|
880
|
+
machine.close(@wfd)
|
|
881
|
+
assert_nil conn.read_int_be(4)
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def test_io_read_int_be_invalid_length
|
|
885
|
+
machine.write(@wfd, (1..8).to_a.pack('C*'))
|
|
886
|
+
assert_raises(UM::Error) { conn.read_int_be(-1) }
|
|
887
|
+
assert_raises(UM::Error) { conn.read_int_be(20) }
|
|
888
|
+
assert_raises(UM::Error) { conn.read_int_be(9) }
|
|
889
|
+
|
|
890
|
+
# 8 is my limit on Schnitzengruben
|
|
891
|
+
value = conn.read_int_be(8)
|
|
892
|
+
assert_equal 0x0102030405060708, value
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
def test_io_read_uint_be
|
|
896
|
+
machine.write(@wfd, "abcd")
|
|
897
|
+
conn.read(4)
|
|
898
|
+
|
|
899
|
+
machine.write(@wfd, "\x00\x01\x05\xdf")
|
|
900
|
+
value = conn.read_uint_be(2)
|
|
901
|
+
assert_equal 0x0001, value
|
|
902
|
+
value = conn.read_uint_be(2)
|
|
903
|
+
assert_equal 0x05df, value
|
|
904
|
+
|
|
905
|
+
machine.write(@wfd, "\x00\x01\x05\xdf")
|
|
906
|
+
value = conn.read_uint_be(4)
|
|
907
|
+
assert_equal 0x000105df, value
|
|
908
|
+
|
|
909
|
+
machine.write(@wfd, "\xde\xfa")
|
|
910
|
+
machine.snooze
|
|
911
|
+
machine.write(@wfd, "\xdd\x03")
|
|
912
|
+
machine.snooze
|
|
913
|
+
value = conn.read_uint_be(4)
|
|
914
|
+
assert_equal 0xdefadd03, value
|
|
915
|
+
|
|
916
|
+
machine.write(@wfd, "\xed\xcb\xa9")
|
|
917
|
+
value = conn.read_uint_be(3)
|
|
918
|
+
assert_equal 0xedcba9, value
|
|
919
|
+
|
|
920
|
+
f = machine.spin do
|
|
921
|
+
machine.write(@wfd, "\xde")
|
|
922
|
+
machine.snooze
|
|
923
|
+
machine.write(@wfd, "\x90\x03")
|
|
924
|
+
end
|
|
925
|
+
value = conn.read_uint_be(3)
|
|
926
|
+
machine.join(f)
|
|
927
|
+
assert_equal 0xde9003, value
|
|
928
|
+
|
|
929
|
+
machine.write(@wfd, "\xff" * 8)
|
|
930
|
+
value = conn.read_uint_be(8)
|
|
931
|
+
assert_equal 0xffffffffffffffff, value
|
|
932
|
+
|
|
933
|
+
machine.write(@wfd, "\x80\x00\x00\x00\x00\x00\x00\x00")
|
|
934
|
+
value = conn.read_uint_be(8)
|
|
935
|
+
assert_equal 0x8000000000000000, value
|
|
936
|
+
|
|
937
|
+
machine.write(@wfd, "\xde\xfa")
|
|
938
|
+
machine.close(@wfd)
|
|
939
|
+
assert_nil conn.read_uint_be(4)
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
def test_io_read_uint_be_invalid_length
|
|
943
|
+
machine.write(@wfd, (0xf1..0xf8).to_a.pack('C*'))
|
|
944
|
+
assert_raises(UM::Error) { conn.read_uint_be(-1) }
|
|
945
|
+
assert_raises(UM::Error) { conn.read_uint_be(20) }
|
|
946
|
+
assert_raises(UM::Error) { conn.read_uint_be(9) }
|
|
947
|
+
|
|
948
|
+
# 8 is my limit on Schnitzengruben
|
|
949
|
+
value = conn.read_uint_be(8)
|
|
950
|
+
assert_equal 0xf1f2f3f4f5f6f7f8, value
|
|
951
|
+
end
|
|
952
|
+
end
|
data/test/test_um.rb
CHANGED
|
@@ -3277,6 +3277,23 @@ class FileWatchTest < UMBaseTest
|
|
|
3277
3277
|
name: 'foo.txt'
|
|
3278
3278
|
}
|
|
3279
3279
|
], events
|
|
3280
|
+
|
|
3281
|
+
fn = File.join(@root, '.bar')
|
|
3282
|
+
IO.write(fn, 'barbar')
|
|
3283
|
+
|
|
3284
|
+
events = machine.inotify_get_events(fd)
|
|
3285
|
+
assert_equal [
|
|
3286
|
+
{
|
|
3287
|
+
wd: wd,
|
|
3288
|
+
mask: UM::IN_CREATE,
|
|
3289
|
+
name: '.bar'
|
|
3290
|
+
},
|
|
3291
|
+
{
|
|
3292
|
+
wd: wd,
|
|
3293
|
+
mask: UM::IN_CLOSE_WRITE,
|
|
3294
|
+
name: '.bar'
|
|
3295
|
+
}
|
|
3296
|
+
], events
|
|
3280
3297
|
end
|
|
3281
3298
|
|
|
3282
3299
|
def write_file(fn, data)
|
|
@@ -3386,6 +3403,25 @@ class FileWatchTest < UMBaseTest
|
|
|
3386
3403
|
{ mask: UM::IN_CREATE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3387
3404
|
{ mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3388
3405
|
], events
|
|
3406
|
+
|
|
3407
|
+
fn = File.join(@root, 'bar/.dotfile')
|
|
3408
|
+
write_file(fn, 'dotdot')
|
|
3409
|
+
machine.sleep(0.05)
|
|
3410
|
+
assert_equal [
|
|
3411
|
+
{ mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'foo.txt') },
|
|
3412
|
+
{ mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'foo/baz.txt') },
|
|
3413
|
+
{ mask: UM::IN_CREATE | UM::IN_ISDIR, fn: File.join(@root, 'bar') },
|
|
3414
|
+
{ mask: UM::IN_CREATE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3415
|
+
{ mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3416
|
+
{ mask: UM::IN_DELETE, fn: File.join(@root, 'foo/baz.txt') },
|
|
3417
|
+
{ mask: UM::IN_DELETE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3418
|
+
{ mask: UM::IN_DELETE | UM::IN_ISDIR, fn: File.join(@root, 'bar') },
|
|
3419
|
+
{ mask: UM::IN_CREATE | UM::IN_ISDIR, fn: File.join(@root, 'bar') },
|
|
3420
|
+
{ mask: UM::IN_CREATE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3421
|
+
{ mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/baz.txt') },
|
|
3422
|
+
{ mask: UM::IN_CREATE, fn: File.join(@root, 'bar/.dotfile') },
|
|
3423
|
+
{ mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/.dotfile') },
|
|
3424
|
+
], events
|
|
3389
3425
|
ensure
|
|
3390
3426
|
machine.schedule(f, UM::Terminate.new)
|
|
3391
3427
|
machine.join(f)
|
|
@@ -3397,22 +3433,26 @@ class SetChildSubreaperTest < Minitest::Test
|
|
|
3397
3433
|
r, w = IO.pipe
|
|
3398
3434
|
UM.pr_set_child_subreaper(true)
|
|
3399
3435
|
|
|
3400
|
-
child_pid = fork
|
|
3436
|
+
child_pid = fork do
|
|
3401
3437
|
r2, w2 = IO.pipe
|
|
3402
|
-
pid = fork
|
|
3438
|
+
pid = fork do
|
|
3403
3439
|
r.close
|
|
3404
3440
|
w.close
|
|
3405
3441
|
w2.close
|
|
3406
3442
|
r2.read
|
|
3407
3443
|
r2.close
|
|
3408
3444
|
sleep(0.01)
|
|
3409
|
-
|
|
3445
|
+
ensure
|
|
3446
|
+
exit!
|
|
3447
|
+
end
|
|
3410
3448
|
w << pid
|
|
3411
3449
|
w.close
|
|
3412
3450
|
r2.close
|
|
3413
3451
|
w2 << 'done'
|
|
3414
3452
|
w2.close
|
|
3415
|
-
|
|
3453
|
+
ensure
|
|
3454
|
+
exit!
|
|
3455
|
+
end
|
|
3416
3456
|
Process.wait(child_pid)
|
|
3417
3457
|
|
|
3418
3458
|
w.close
|
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: 1.0.
|
|
4
|
+
version: 1.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
@@ -103,6 +103,7 @@ files:
|
|
|
103
103
|
- examples/http_server.rb
|
|
104
104
|
- examples/inout.rb
|
|
105
105
|
- examples/io_uring_simple.c
|
|
106
|
+
- examples/irb_fiber.rb
|
|
106
107
|
- examples/nc.rb
|
|
107
108
|
- examples/nc_ssl.rb
|
|
108
109
|
- examples/pg.rb
|